Java虚拟机内存布局
Java内存模型建立在自动内存管理的概念之上。当一个对象不再被一个应用所引用,垃圾回收器就回收它,从而释放相应的内存。JVM从底层操作系统中分配内存,并将它们分为以下几个区域:
- 堆空间(Heap Space):这是共享的内存区域,用于存储可以被垃圾回收的对象;
- 元代(Metaspace):与堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
这块区域被称作“永久代(Permanent Generation)”,用于存储被虚拟机加载的类信息等数据。 - 本地方法栈(Native Area):线程私有。这个区域用于存储基本类型的引用和变量;
- 虚拟机栈:线程私有。
- 程序计数器:线程私有。
堆内存布局
一个有效的管理内存方法是把内存空间划分为不同代,这样垃圾回收器就不用扫描整个堆区。大多数对象的生命周期都很短暂,那些生命周期较长的对象往往直到应用退出后才清除。JVM把堆划分为新生代和老年代,在新生代中,GC可以快速的标记回收“死对象”,而不需要扫描整个Heap中的存活较长时间的“老对象”。
HotSpot又把新生代进一步划分为3个区域:一个相对大一些的区域,称为“Eden区”;两个相对小一点的区域,称为“From幸存区(survivor)”和“To幸存区(survivor)”。当一个Java应用创建了一个对象,这个对象就被存储到Eden中(如果对象过大,会被存储在老年代中)。一旦新生代存储满了,就会在新生代触发一次minor gc(小范围的垃圾回收)。新生代的GC使用复制算法。在GC前,To幸存区(survivor)会被清空,对象保存在Eden区和From幸存区(survivor)中。GC运行时,Eden中的幸存对象被复制到To幸存区(survivor)。针对From幸存区(survivor)中的幸存对象,会考虑对象年龄,如果没有达到阈值(tenuring threshold),对象会被复制到To 幸存区(survivor)。如果达到阈值,则被复制到老年代中。复制阶段完成后,Eden 和From 幸存区中只保存死对象,可以视为清空。如果在复制过程中To 幸存区被填满了,剩余的对象会被复制到老年代中。最后 From 幸存区和 To幸存区会调换下名字,在下次GC时,To 幸存区会成为From 幸存区,如下图所示,其中黄色表示死对象,绿色表示剩余空间,红色表示幸存对象。
当一个对象存活到一定的周期后,它就会被移动到堆中的老年代(tenured pool)。最后,当老年代被填满时,就会被触发一次full gc或者major gc(完全的垃圾回收),以清理老年代。
一般我们把初生池和幸存池所在的区域合并成为新生代,把老年代所在的区域称为老年代。对应的,在新生代上产生的gc称为minor gc,在老年代上产生的gc称为full gc。当垃圾回收执行的时候,所有的应用线程都要被停止,系统产生一次暂停。minor gc非常频繁,所以被优化的能够快速的回收死对象,是新生代的内存的主要回收方式。major gc运行起来用相对慢得多,因为要扫描非常多的活着的对象。