JVM之GC(三)
前面介绍了GC和几种主流的GC算法,这节准备说一下垃圾收集器。垃圾收集器可以分为三类,Yong GC, Old GC, Mixed GC
Yong GC
1、Serial
单线程处理,采用复制算法,通常运行在Client模式下,触发STW
2、ParNew
多线程处理,采用多个线程来执行复制算法,通常运行在Server模式下,触发STW,一般与CMS搭配
3、Parallel Scavenge
可以控制STW时间的收集器,时间设置的越短触发GC的频率也就越高,采用的也是复制算法
Old GC
1、Serial Old
单线程,标记整理算法,STW;可以与上3搭配使用,也可以在CMS触发Full GC时使用
2、Parallel Old
多线程,STW,同上3,它俩搭配使用
3、CMS
主流老年代收集器,以最短停顿时间为目标。标记清除法,GC过程为:初始标记(单线程,耗时短,STW)、并发标记(与用户线程一起执行,耗时长)、重新标记(多线程,STW,用来处理前一步用户所产生的新引用)、并发清除(与用户线程一起执行,耗时长)
CMS三大特点:
CMS默认启动回收线程数量为(CPU数量+1)/3,电脑核数越少对程序影响越大(因为并发标记会占用一部分用户资源);
CMS无法处理浮动垃圾,在并发标记期间用户产生的新的垃圾当次是无法处理的,只能放到下次,因此CMS无法做到空间不足时才GC,它在触发GC之前必须为用户预留足够的空间,否则会报错“Concurrent ModeFailure”,JVM会临时启动备用垃圾收集器串行进行清理(如Serial Old),这次停顿时间反而更长
CMS基于标记清除,因此会碎片化堆区,当无法为大对象无法进入老年代时便会触发Full GC,,JVM提供了参数-XX:+UseCMSCompactAtFullCollection 开关FullGC过程中的内存合并整理功能, -XX:CMSFullGCsBeforeCompaction设置执行多少次不压缩的FullGC后跟着来一次压缩的。
Mixed GC
G1收集器
它是目前最前沿的垃圾收集器,它可以单独使用,具有以下特点:并行与并发(充分利用多核多CPU)、分代收集(yong old)、空间整合(标记整理)、可预测的停顿。它是如何做到的呢?
它把整个Java堆区分为多个大小相等的独立区域(Region),仍保留了老年代和年轻代的概念,但以Region分区为主;它会将各个Region里面的垃圾回收的价值(可释放空间与预测的GC时间)进行排序,在后台维护一个优先列表,在可允许的时间内会优先回收价值最大的Region,这样就达到了高效且时间可控的目的。
读到这里不知道大家有没有发现一个问题,虽然将堆区划分为年轻代和老年代,因为老年代和年轻代是可以相互引用的,那么在可达性判定时JVM是不是仍需要扫描整个堆区才足够安全?扫描的话MinorGC效率会大打折扣,那么在GC频繁的年轻代如何避免老年代所引用的对象被错删呢?其实在老年代有一个Write barrier(写屏障)和card table,card table中存放了所有的老年代对年轻代的引用,所以每次MinorGC的时候查询这个table就可以了。
在G1中是如何处理这个问题的呢?答案是Remembered Set,G1的每个Region都有一个与之对应的Remembered Set,在对引用类型的数据进行写操作时,通过写屏障暂时中断写操作,然后检查这个引用类型所引用的对象是否处于不同的Region中,如果是,便通过Card table把相关引用信息记录到被引用对象所属的Region的Remembered Set中,在GC时,把Set中的对象也考虑进根节点便可以了。
G1收集器的操作流程:初始标记(STW,短)、并发标记(与用户线程并发,长)、最终标记(类似于CMS的重新标记,将浮动垃圾写入Rem Set log然后合并到Rem Set)、筛选回收(按价值排序然后回收)
在这里引用书中的一句话:从JDK1.3到现在,从Serial收集器-》Parallel收集器-》CMS-》G1,用户线程停顿时间不断缩短,但仍然无法完全消除;