【JVM】Java内存详解:堆和栈的区别

时间:2021-05-22 15:09:42

最近在研究多线程的东西,看到了Java内存的相关知识。又回到了堆和栈这个话题,在很早之前就研究过,只知道这两种数据结构一个是先进后出,一个是先进先出,借这个机会,再细致研究一下,对比二者的不同:

Java把内存划分为两种:一种是栈内存,一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作他用。

堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象,引用变量就相当于是为数组或对象起的一个名称。引用变量是普通变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候才会变成垃圾,不能再被使用,但仍然占据着内存空间,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是Java比较占内存的原因,实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针。

堆Heap:
1. 和栈一样存储在计算机RAM中
2. 在堆上的变量必须手动释放,不存在作用域的问题。数据可用delete、delete[]或者free来释放
3. 相比在栈上,堆分配内存要慢
4. 通过程序按需分配 大量的分配和释放可能造成内存碎片
5. 可能会造成内存泄漏(内存空间使用完毕后未回收,一直占据内存单元)
6. 如果申请的缓冲区过大的话,可能申请失败
7. 在运行期间如果你不知道会需要用多大的数据或者你需要分配大量内存的时候,建议使用堆

栈Stack:
1. 和堆一样存储在计算机RAM中
2. 在栈上创建变量的时候会扩展,并且会自动回收
3. 相比堆而言在栈上分配的要快很多
4. 用数据结构中的栈实现
5. 存储局部的数据,返回地址,用做参数传递
6. 当用栈过多的时候可导致栈溢出(无穷次(大量的)的递归调用,或者大量的内存分配)
7. 在栈上的数据可以直接访问(不是必须使用指针访问)
8. 如果你在编译之前精确的知道你需要分配数据的大小并且不是太大的时候,可以使用栈
9. 当你程序启动的时候决定栈的容量上限

其实堆和栈是两种内存分配的两个统称,可能有很多种不同的实现方式,但要符合两个基本的概念:
栈:先进先出原则,栈中新添加的数据放在其他数据的顶部,移除的时候你也只能移除最顶部的数据,不能按位获取
堆:先进后出的原则,数据项位置没有固定的顺序,你可以以任何顺序插入和删除,因为他们没有“顶部”数据这一概念。

Java中变量在内存中的分配:
类变量:static修饰的变量,在程序加载时系统就为它在堆中开辟了内存。堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期:一直持续到整个“系统”关闭
实例变量:当你使用Java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为 一长串数字以表征这个变量在堆中的“物理位置”。实例变量的生命周期:当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收”名单”中,但并不是马上就被释放堆中内存

最后可能会问这样一个问题,栈为什么比堆快?
因为栈的存储模式使它可以轻松的分配和重新分配内存(指针/整型只是进行简单的递增或者递减运算),然而堆在分配和释放的时候有更多的复杂的bookkeeping参与。另外在栈上的每个字节频繁的被复用也就意味着它可能映射到处理器缓存中,所以栈比堆速度快。

其实对比堆和栈,只要把握一个重要的信息,new的对象是存储在堆中的,但为了存取方便,会在栈中声明一个引用变量指向堆中的对象,这样存取方便而且速度快。另一方面,栈中的对象超过作用域即被释放,Java会立即释放掉内存空间另作他用;而堆中即使超出变量的作用范围也不会变成垃圾,只有在没有引用变量指向它时会变成垃圾,但又不会立刻被回收,只有在一个不确定的时间才会被垃圾回收器回收掉,这也就是为什么Java会比较占用内存的原因了。再一点还要注意,就是被static修饰的变量,它是在程序启动之初就会被分配内存,它的生命周期会一直到程序结束,所以使用static一定要慎重。