- 内存中的栈空间与堆空间
https://www.jiuzhang.com/tutorial/algorithm/352
我们通常所说的内存空间,包含了两个部分:栈空间(Stack space)和堆空间(Heap space)
当一个程序在执行的时候,操作系统为了让进程可以使用一些固定的不被其他进程侵占的空间用于进行函数调用,递归等操作,会开辟一个固定大小的空间(比如 8M)给一个进程使用。这个空间不会太大,否则内存的利用率就很低。这个空间就是我们说的栈空间,Stack space。
我们通常所说的栈溢出(Stack Overflow)是指在函数调用,或者递归调用的时候,开辟了过多的内存,超过了操作系统余留的那个很小的固定空间导致的。那么哪些部分的空间会被纳入栈空间呢?栈空间主要包含如下几个部分:
- 函数的参数与返回值
- 函数的局部变量
我们来看下面的这段代码:
public int f(int n) {
int[] nums = new int[n];
int sum = 0;
for (int i = 0; i < n; i++) {
nums[i] = i;
sum += i;
}
return sum;
}
根据我们的定义,参数 n,最后的函数返回值f,局部变量 sum 都很容易的可以确认是放在栈空间里的。那么主要的难点在 nums。
这里 nums 可以理解为两个部分:
- 一个名字叫做 nums 的局部变量,他存储了指向内存空间的一个地址(Reference),这个地址也就是 4 个字节(32位地总线的计算机,地址大小为 4 字节)
- new 出来的,一共有 n 个位置的整数数组,int[n]。一共有 4 * n 个字节。
这里 nums 这个变量本身,是存储在栈空间的,因为他是一个局部变量。但是 nums 里存储的 n 个整数,是存储在堆空间
里的,Heap space。他并不占用栈空间,并不会导致栈溢出。
在大多数的编程语言中,特别是 Java, Python 这样的语言中,万物皆对象,基本上每个变量都包含了变量自己和变量所指向的内存空间两个部分的逻辑含义。
来看这个例子:
public int[] copy(int[] nums) {
int[] arr = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
arr[i] = nums[i]
}
return arr;
}
public void main() {
int[] nums = new int[10];
nums[0] = 1;
int[] new_nums = copy(nums);
}
在 copy 这个函数中,arr 是一个局部变量,他在 copy 函数执行结束之后就会被销毁。但是里面 new 出来的新数组并不会被销毁。
这样,在 main 函数里,new_nums 里才会有被复制后的数组。所以可以发现一个特点:
栈空间里存储的内容,会在函数执行结束的时候被撤回
简而言之可以这么区别栈空间和堆空间:
new 出来的就放在堆空间,其他都是栈空间