垃圾回收主要回收的是堆内存,基于分代的思想,分为新生代和老年代。本篇主要涉及到JVM中对象分配、对象晋级、对象的生死判定、对象回收、垃圾回收器以及一些垃圾回收的面试题。
目录
垃圾回收的一些基本概念
■JVM中堆内存分配
■ 对象分配
☛ 优先在Eden区分配。当Eden区没有足够空间分配时,VM发起一次Minor GC(回收新生代垃圾),将Eden区和其中一块Survivor区内尚存活的对象放入另一块Survivor区域。如Minor GC时,Survivor空间不够,对象提前进入老年代,老年代空间不足时进行Full GC;
☛ 大对象直接进入老年代,避免在Eden区和Survivor区之间产生大量的内存复制,此外大对象容易导致还有不少空闲内存就触发GC以获得足够的连续内存空间(数组)。
注:Minor GC相当于是轻量级的垃圾回收,VM频繁的进行Minor GC是无所谓的,而应当避免Full GC,因为尽管随着jdk版本的提升,GC效率越来越高,但是仍然是很消耗性能的!
■ 对象晋级
☛年龄阈值:VM为每个对象定义了一个对象年龄(Age)计数器。经第一次Minor GC后仍然存活,被移到Survivor空间中,并将年龄设置为1。以后对象在Survivor区中每熬过一次Minor GC年龄就+1,当增加到一定程度(-XX:MaxTenuringThreshold,默认15),将会晋升到老年代。
☛ 提前晋升:动态年龄判定,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而无需等到晋升年龄。
■ 对象生死判定
可达性分析算法 —— 通过一系列的称为GC Roots的对象作为起点,然后向下搜索;搜索所走过的路径称为引用链/Reference Chain,对象当一个到GC Roots没有任何引用链相连时,即该对象不可达,也就说明此对象是不可用的。
对象回收方法论——分代收集
■ 新生代——标记清除法
该算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象(可达性分析),在标记完成后统一清除掉所有被标记的对象。
缺点:①效率问题 —— 标记和清除的效率都不高;
②空间问题 —— 标记和清除操作后会产生大量不连续的内存碎片,空间碎片太多可能会导致在运行过程中需要给较大对象分配内存时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收。
■ 新生代——复制算法
该算法的核心是将可用内存按容量划分成大小相等的两块,每次只用其中一块,当这一块的内存用完,就将还存活的对象复制到另一块上,然后把使用过的内存空间一次性清理掉。
优点:①由于每次都对整个半区进行内存回收,内存分配不必考虑内存碎片问题;
②垃圾回收后内存连续,只需要移动堆顶指针,按顺序分配内存即可;
③特别适合java对象朝生夕死的特点。
缺点:①内存大小减为原来的一半,太浪费了;
②对象存活率较高的时候,需要执行较多的复制操作,效率变低;
③如果不使用50%的对分策略,老年代需要考虑空间的担保策略。
■ 老年代——标记整理算法
该算法分为“标记”和“清除”两个阶段,首先标记出所有要回收的对象(可达性分析),在标记完成后让所有存活的对象向一端移动,然后清理掉端边界以外的对象。
优点:①不会损失50%的空间;
②垃圾回收后内存连续,只需要移动堆顶指针,按顺序分配内存即可;
③比较适合有大量存活对象的垃圾回收;
缺点:标记/整理算法唯一的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上讲,标记整理算法的效率低于复制算法。
垃圾回收器
收集器 | 收集对象和算法 | 收集器类型 | 说明 | 适用场景 |
Serial(最古老的) | 新生代 复制算法 | 单线程 | 进行垃圾回收时,必须暂停掉所有工作线程,直到完成;(Stop the world耗时长,后面的几种回收器主要是为了减短这个时间); | 简单高效,适合内存不大的情形; |
ParNew | 新生代 复制算法 | 并行的多线程收集器 | ParNew垃圾收集器是Serial的多线程版本; | 运行在server模式下的虚拟机首选; 搭配CMS垃圾回收器的首选; |
Parallel Scavenge 吞吐量优先收集器 | 新生代 复制算法 | 并行的多线程收集器 | 类似ParNew,更加关注吞吐量,达到一个可控制的吞吐量,jdk默认的新生代垃圾收集器; | 本身是Server级别多CPU机器上的默认GC方式,良好的响应速度能够提高用户体验,主要适合后台运算不需要太多交互的任务; |
Serial Old MSC | 老年代 标记整理算法 | 单线程 | Client模式下,虚拟机使用 | |
Parallel Old | 老年代 标记整理算法 | 并行的多线程收集器 | Parallel Scavenge收集器的老年代版本,为了配合Parallel Scavenge的面向吞吐量的特性而开发的对应组合,JDK的默认老年代垃圾回收器; | 在注重吞吐量和CPU资源敏感的场合使用; |
CMS(Jdk8及之前是主流的) | 老年代 标记清除算法 | 并行与并发的收集器 | 尽可能缩短垃圾回收时用户线程停止时间。缺点(标记清除算法造成的):1.内存碎片,2.需要更多CPU资源,3.浮动垃圾问题,需要更多的堆空间; | 重视服务的响应速度、系统停顿时间和用户体验的互联网网站或者B/S系统,互联网后端CMS是主流的垃圾回收器(Jdk9之前); |
G1(Jdk7引入,Jdk9成为默认) | 跨新生代和老年代 标记整理+化整为零 | 并行与并发的收集器 | JDK1.7正式引入,采用分区回收的思维,基本不牺牲吞吐量的前提下完成低停顿的内存回收;可预测的停顿是其最大的优势。 | 面向服务端应用的垃圾回收器;目标为取代CMS。 |
注:吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收时间);
垃圾回收时间=垃圾回收频率*单次垃圾回收时间。
G1对堆内存的划分不同于前面对新生代和老年代一整块一整块划分,而是细分成一小块一小块的,内存划分示意图如下:
垃圾回收相关面试题
★垃圾回收常用算法,特点?
新生代-标记清除算法、新生代-复制算法、老年代-标记整理算法。(特点上面有总结)......
★常见垃圾回收器,优缺点,重点讲CMS?(见上面的表)
★完成一次GC的流程(从ygc到fgc),重点讲讲对象如何晋升到老年代?
★JVM垃圾回收机制,何时触发MinorGC和FullGC?
从新生代(包括Eden和Survivor区)回收内存被称为Minor GC;对老年代GC称为Major GC,而FullGC是对整个堆来说的,FullGC时间比较慢,而且会占用CPU的时间片。
Minor GC触发条件 —— 当Eden区满时,触发Minor GC;
Full GC触发条件(避免) ——
①System.GC();
②老年代空间不足;
③永生区(JDK8中无永生区了)空间不足;
④统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间;
⑤堆中分配很大的对象(指需要大量连续内存空间的java对象,例如很长的数组)。详见https://www.cnblogs.com/sunshisonghit/p/7448224.html