jvm哪几种垃圾收集器,各自的优缺点,包括原理,流程?
(题外话:本人电脑堆初始化250M左右,最大堆3.5GB左右可使用java -XX:+PrintCommandLineFlags -version查看,查看新生代老年代的空间:java -XX:+PrintGCDetails -version,jdk8默认垃圾回收组合为UseParallelGC 即 Parallel Scavenge + Parallel Old)
有分代的垃圾收集器,需要新生代和老年代垃圾收集器的相互配合;使用垃圾收集器时要注意各种垃圾收集器的组合
A.Serial收集器:单线程处理器,回收垃圾时必须stop the world,简单高效,是客户端模式下的新生代收集器
B.ParNew收集器:多线程处理器,Serial收集器的服务端版本,复制算法,只有它和Serial才能配合CMS处理器,JDK9及之后parNew+CMS不再是官方默认的服务端组合收集器,ParNew合并入了CMS成为第一款退出历史舞台的垃圾收集器
C.Parallel Scavenge收集器:新生代收集器,标记复制算法,多线程,与之前的收集器不同,它关注的是系统的吞吐量。可以通过-XX:MaxGCPauseMillis(允许每次回收垃圾的时间(单位:毫秒))、-XX:GCTimeRatio(吞吐量率:1-99的整数,默认为99)和-XX:+UseAdaptiveSizePolicy(开启自适应策略:开启后只需要给定基础的默认值如堆的最大,吞吐量或者垃圾回收时间制定一个优化目标,其他交给虚拟机处理即可)
D.Serial Old收集器:是serial收集器的老年代版本、单线程、标记整理算法。客户端下的HotSpot使用、jdk5之前与Parallel Scavenge收集器搭配使用、作为CMS收集器失败后备预案。(在服务端就是鸡肋的存在)
E.Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,多线程,标记-整理,只能和Parallel Scavenge搭配使用,直到它的出现才有了比较名副其实的吞吐量优先的垃圾收集器组合出现,之前的Parallel Scavenge因为serial Old在性能上的拖累造成某些环境的吞吐量甚至不如ParNew+CMS优秀(服务器性能好的直接上ParNew+CMS)
F.CMS收集器:获取最短停顿时间为目标的收集器、标记-清除、老年代、jdk5中发布
初始标记:需要stop the world标记GC root关联对象
并发标记:时间较长,跟用户线程并发,使用增量更新算法
重新标记:标记那些在并发标记中多出来的变量,时间比初始标记稍稍长一点
并发清除:和用户线程一起并发清除那些判断死亡的对象。
缺点:1.对资源很敏感,因为CMS默认启动回收线程数是(处理器核数+3)/4,在处理器核心少于4时,且应用程序负载较高时,会导致用户程序执行大幅降低
2.CMS无法处理浮动垃圾,因为在并发清理的时候,用户线程还在继续,所以也就会有源源不断的对象产生,这些对象在当次垃圾回收中是无法被回收的。CMS默认在老年代占了92%的时候开始清理垃圾(因为CMS是并发清理垃圾的需要时间,可以通过-XX:CMSInitiatingOccu-pancyFraction来设置当占多少占比的时候开始清理垃圾),如果垃圾清理时运行容量不足,就会临时采用Serial Old来进行老年代的垃圾收集(上面说你鸡肋,我错了TT,)
3.会产生大量的空间碎片,JVM对此提供了两个参数(一个是不得不触发FullGC时强制整理内存,另外一个是Full GC多少次后整理内存再Full GC,均在jdk9之后开始废弃),后面默认每次进入Full GC的时候都要进行碎片整理
G.G1收集器:基于Region内存布局形式(将内存划分为多个大小不一的2的N次幂的区域,根据回收区域的经验值和用户期待的暂停时间来进行垃圾回收)、G1取代jdk8的默认组合、G1默认的暂停时间为200毫秒、G1还是保留了新生代和老年代的概念,但是区别已经没那么大了,比如超过半个Region区域大小的对象就被定义为大对象,被存放在Humongous Region中,G1大部分行为将其视为老年代的一部分来处理。G1和CMS之间的堆平衡点大概是在6-8GB左右
回收特点:从整体来看G1是标记-清除算法,从Region之间来看G1是标记-复制算法。这种方法也意味着G1在运行期间不会产生浮动垃圾和内存空间碎片。
过程:
1.初始标记:标记GC root能关联到的对象,并修改TAMS指针的值,借助Minor GC同步完成
2.并发标记:从GC root开始对堆对象进行可达性分析,此时产生的对象都是被隐式标识过的默认在并发标识中不标记,但是要处理SATB指针有变动的对象,使用原始快照算法
3.最终标记:短暂的暂停,处理遗留的少量并发标记中里面的对象
4.筛选回收:负责更新Region的统计数据,按照用户的期望值将各个Region按照回收成本和回收价值进行排序回收,将存活的对象复制到空的Region中,再将整个Region回收。这个过程必须暂停用户线程。
G1的问题:
1.Region之间的对象可能存在跨区引用,如果不对其进行优化的话,就可能造成全堆扫描,全堆扫描代价太大,G1在每个Region都维护了一个记忆集,用来记录Region中的指向别的Region对象以及其他指向本Region的对象。所以G1比较传统的垃圾收集器有20%左右的内存负担。
2.G1在垃圾回收时,通过SATB算法来实现,维护两个指针,用来存储在并发清理时产生的垃圾,跟CMS一样,如果回收的速度超出内存分配的速度或者容量不足时,G1收集器就会被迫冻结用户线程的执行,导致Full GC产生长时间的stop the world
H.Shenandoah收集器:非官方、和G1很相似,同样使用Region布局,同样有大对象Humongous Region,默认也是回收价值最大的Region。不同在于,Shenandoah默认不使用分代收集、抛弃了记忆集采用连接矩阵、工作流程的不同:
1.初始标记:标记GC root关联对象,会有短暂停顿
2.并发标记:标记出所有可达的对象
3.最终标记:扫描处理剩下的SATB对象,会有短暂停顿
4.并发清理:清理那些一个活对象都没有的Region
5.并发回收:与用户线程并发,回收线程将存活的对象复制到新的Region区要注意复制对象时,用户线程也在进行,就可能造成读写对象不一致的问题,这个时候主要是通过一个对象头指针Brooks printer和读写屏障(读和写时的AOP)来实现读写一致的问题
6.初始引用更新:将堆中指向旧对象的引用更新,确保每个收集器线程都完成了分配给它们移动对象的任务
7.并发引用更新:真正更新引用操作,按照内存地址顺序,将引用更新即可
8.最终引用更新:修改存在GC root中的引用,最后一次停顿
9.并发清理:跟步骤4一样清理数据
读者可以简单理解为:并发标记、并发回收、并发引用更新三个步骤
H:ZGC收集器:这个实现和原理会比较复杂,推荐看美团技术团队在知乎写的文章,比较深刻也有很多案例:https://zhuanlan.zhihu.com/p/170572432