JVM内存分配和回收
本文要求读者对Java虚拟机(JVM)内部结构比较熟悉,了解虚拟机运行时有哪些数据区域,垃圾回收算法以及垃圾回收器。在此基础上本文对JVM内存分配和回收的相关知识进行梳理和总结。
程序计数器、虚拟机栈、本地方法栈的内存分配与回收
程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。
每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性。
这三个区域不需要过多的考虑回收的问题,因为方法结束或线程结束时,内存自然就随着回收了。
Java堆和方法区的内存分配与回收
Java堆中存储的是实例数据(即对象),主要是成员变量及其值。
方法区中存储的是类型数据,主要是类信息、常量、静态变量、即时编译器编译后的代码等。
一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,JVM只有在程序运行期间才能知道会创建哪些对象,因此Java堆和方法区的内存分配和回收都是动态的,是需要重点讨论的地方。
类型数据和实例对象的内存分配
类型数据 ——> 方法区
- 什么时候JVM将类加载到方法区?
- 调用一个类的静态变量或方法时。
- 创建一个对象而方法区又没有相应的类型数据时。
实例对象 ——> 新生代Eden区
- 新生的对象一般在新生代Eden区分配内存。
实例对象 ——> 老年代
-
新生的对象进入老年代
- 若新生的对象太大在新生代Eden区分配失败,且该对象是一个不含任何引用的数组,则直接在老年代分配,从而避免一次 Minor GC。
- 若新生的对象比较大,超过了 PretenureSizeThreshold 参数的设置值,则直接在老年代分配内存,从而避免了 Minor GC 时对大对象的复制。(PretenureSizeThreshold 默认值为0,意味着无论对象多大都只在新生代Eden区分配内存)
-
新生代的对象进入老年代
- 若新生代中对象的年龄(经历 Minor GC 的次数)达到 MaxTenureThreshold 参数设置值,该对象会被移到老年代。
- Minor GC时,若From Survivor中相同年龄对象达到或超过该区域容量的一半,该区域中大于等于该年龄的对象即使年龄没有达到 MaxTenureThreshold 参数设置值,也会直接移到老年代。
- Minor GC失败时,会触发空间分配担保(见附录),若担保成功,也会有新生代中的对象移到老年代。
实例对象 ——> 本地线程分配缓存
- 若启动了本地线程分配缓存(Thread Local Allocation Buffer, TLAB),则优先在TLAB上为对象分配内存。
实例对象 ——> 虚拟机栈
- 若虚拟机检测到一个对象在运行期间的作用范围不会超过一个方法或一个线程的作用域(即进行逃逸分析),会把这个对象拆解成其内包含的若干成员变量(即进行标量替换),从而在虚拟机栈上分配内存,节省了Java堆的空间。以上过程是即时编译技术(Just In Time, JIT)的一种优化情形。
注意:新生代Eden区、老年代和TLAB都在Java堆中。
类型数据和实例对象的内存回收
方法区中的废弃常量和无用的类是通过Full GC进行回收的。
Java堆中的无用对象是通过Minor GC、Major GC和Full GC进行回收的。
Minor GC
- 介绍:回收Java堆新生代区域无用对象,新生代回收器采用复制算法。
- 触发:当Eden区没有足够的空间分配新产生的对象时,虚拟机将发起一次 Minor GC。
- 过程:Minor GC 触发后会将新生代Eden区和From Survivor区中存活的对象,复制到To Survivor区。
- Minor GC失败:To Survivor区剩余容量小于新生代Eden区和From Survivor区中存活的对象总容量时,Minor GC失败,会触发空间分配担保(见附录)。
Major GC
- 介绍:回收Java堆老年代区域无用对象,老年代回收器大多采用标记-整理算法,而CMS回收器采用标记-清除算法。
- 触发:大多数老年代回收器在老年代区域被填满时会触发 Major GC,CMS回收器是达到 CMSInitiatingOccupancyFraction 参数设置的占用率时触发 Major GC。
- 过程:老年代回收器大多采用标记-整理算法(CMS回收器采用标记-清除算法)清理老年代区域无用对象。
Full GC:
- 介绍:全面回收Java堆新生代、老年代和方法区(永久代)的无用数据。
- 触发:
- 在程序中直接调用System.gc。
- 方法区(非堆)空间用完时。
- CMS收集器无法处理浮动垃圾而导致“Concurrent Mode Failure”时。
- 空间分配担保(见附录)过程中可能出现Full GC。
- 过程:新生代回收器和老年代回收器分别对新生代区域和老年代区域清理无用对象,JVM清除方法区中的废弃常量并卸载无用的类。
附录
空间担保分配过程
参考
[1] 《深入理解Java虚拟机》
[2] -XX:PretenureSizeThreshold的默认值和作用浅析
[3] JVM 触发Full gc条件
[4] JIT—即时编译