7个经典垃圾回收器:
新生代垃圾回收器:Serial,ParNew,Parallel Scavenge
老年代垃圾回收器:CMS,Serial Old,Parallel Old
整堆垃圾回收器:G1
1.JDK8.0中,已经废弃了Serial-CMS和ParNew-Serial Old组合,并在JDK9.0中移除了这些组合;
2.JDK14中,废弃了Parallel Scavenge-Serial Old组合,移除CMS垃圾回收器;
Serial回收器:
- Serial:Hotspot中client模式下默认新生代垃圾回收器,采用复制算法和串行回收机制执行内存回收;
- Serial Old:Hotspot中client模式下默认老年代垃圾回收器,采用标记-压缩算法和串行回收机制执行内存回收;
ParNew回收器:
- ParNew:(多线程)并行回收;
Parallel Scavenge回收器:
- Parallel Scavenge:并行回收;
- 与ParNew的区别:吞吐量优先,达到一个可控制的吞吐量;自适应调整策略;
CMS回收器:
- CMS特点:强交互,低延迟
- 是一款并发收集器,实现了用户线程和垃圾收集线程同时运行;
- 分为四个阶段:
初始标记(是STW的,主要是标记出GC Roots直接关联到的对象,速度快)
并发标记(从GC Roots直接连接对象开始遍历整个对象图,耗时长,但是与用户线程并发运行)
重新标记(修正在并发标记期间,因为用户线程继续运行而导致标记变动的那部分对象的标记记录)
并发清除(清理标记阶段判断已经死亡的对象); - 采用了标记-清除算法执行内存回收;
- CMS的弊端:
由于采用标记-清除算法,会产生内存碎片,在无法分配大对象的情况下,会触发Full GC;
对CPU资源非常敏感,在并发阶段,虽然不会导致用户线程停顿,但是因为占用了一部分线程导致应用程序变慢,总吞吐量降低;
无法处理浮动垃圾,可能出现"Concurrent Mode Failure"导致另一次Full GC的产生;浮动垃圾就是那些在并行标记期间还在运行的用户线程产生的垃圾对象,CMS无法对这些对象标记,只能在下次执行GC时释放;
G1回收器:
-
特点:
延迟可控下获得尽可能高的吞吐量;
区域分代化;
将堆内存分为若干个Region,这些区域包含了逻辑上的新生代和老年代,垃圾回收时同时兼顾新生代和老年代;
可预测的停顿时间模型(软实时soft real-time),能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾回收的时间上不超过N毫秒;G1回收器怎么解决一个对象被不同区域引用的问题? JVM使用Remembered Set 来避免全局扫描; 1.每个Region都有一个对应的Remembered Set 2.每次Reference类型数据写操作时,会产生一个Write Barrier暂时中断操作 3.然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(如果是其他收集器,就会检查老年代是否引用了新生代对象) 4.如果不同,就会通过CardTable把相关引用信息记录到引用指向对象的所在Region的Remembered Set中 5.当进行GC时,在GC Roots的枚举范围加入Remembered Set,以保证不进行全局扫描,也不会有遗漏;
-
回收过程:
1.年轻代GC
第一阶段:扫描根,根引用连同Rset记录的外部引用都作为扫描存活对象的入口;
第二阶段:处理dirty card queue,更新Rset;
第三阶段:处理Rset;识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象;
第四阶段:复制对象;遍历对象树,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到Old区中空的内存分段;
第五阶段:处理引用;处理Soft,Weak,Phantom,Final,JNI Weak 等引用;
2.并发标记
初始标记:标记从根节点直接可达的对象。这个阶段是STW的,会触发一次年轻代GC;
根区域扫描:扫描Survivor区直接可达的老年代区域对象,并标记为被引用的对象。这一过程需要在young GC之前完成;
并发标记:在整个堆中进行并发标记,可能被young GC中断。若发现区域对象中所有对象都是垃圾,那么这个区域被立即回收。同时在并发标记过程中,会计算每个区域的对象活性(存活对象的比例)
再次标记:修正上一次的标记结果。使用SATB开始时快照技术;(STW)
独占清理:计算各个区域的存活对象和GC回收比例,并进行排序,识别可混合回收的区域;这个阶段并不会做实际意义上的垃圾收集;(STW)
并发清理:识别并清理完全空闲(100%垃圾的内存分段)的区域;
3.混合回收
并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次被回收。混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。
由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收(-XX:G1MixedGCLiveThresholdPercent),默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。
混合回收并不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。