开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情
垃圾回收器 Part1
本节是垃圾回收器的第一部分,由于垃圾回收器相关的内容比较多,所以拆分成两个部分。这一部分将围绕垃圾回收算法,常见的几种垃圾回收器,比如Serial,Parallel,CMS。
垃圾回收算法
常见的垃圾回收算法有三种,分别是标记复制法,标记清除法与标记整理法。
标记复制法
对于标记复制法,它会将内存分成两半,每次使用其中一半,这一半使用完毕后将存活的对象复制到另一半,然后把原来使用的部分清空。
回收前
回收后
如果每次都是划分一半的话就太浪费空间了,大部分对象都是朝生夕灭的,因此实际应用中是像前面文章中提到的Eden区和Survivor区按照8:1:1的比例进行分配。新对象在Eden区创建,存活下来后会被复制到Survivor区中。
标记清除法
标记清除算法会将会标记所有存活的对象,然后清除掉所有未被标记的对象。或者标记所有未存活的对象,然后清除到被标记的对象。但是通常使用前者,因为绝大多数对象都是朝生夕灭的,前者标记的数量更少,效率也会更高。
回收前
回收后
如图可见这种方法虽然简单,但是会带来大量的碎片空间,如果出现较大的对象虽然剩余空间足够会由于碎片原因无法容纳。
标记整理法
为了解决标记清除法产生的空间碎片,我们在原来的标记清除法的基础上对存活对象进行整理。将存活对象移向一端,然后直接清理掉边界外的内存。回收前的图和标记清除法一致。
回收后
以上三种算法,由于新生代垃圾回收比较频繁,而且大部分是朝生夕灭的,所以选择的是标记复制法,而老年代则是选择标记清除法和标记整理法。
下面将开始介绍几种常见的垃圾回收器
Serial
该垃圾回收器通过参数 -XX:+UseSerialGC和 -XX:+UseSerialOldGC开启。
Serial垃圾回收器从名字上就可以看出这是一个单线程的垃圾回收器,这个单线程不仅仅意味着在回收过程只有一个线程去完成垃圾回收工作,同时也意味着在垃圾回收过程中需要STW(Stop the world)直至垃圾回收结束。
年轻代使用的是标记复制法,老年代使用的是标记整理法。
STW会带来不良的用户体验,但是Serial由于是单线程没有线程切换的消耗而且简单高效因此具备很好的单线程回收效率。
Parallel
该垃圾回收器通过参数 -XX:+UseParallelGC和 -XX:+UseParallelOldGC开启。
Parallel垃圾回收器是一个多线程垃圾回收器也是JDK 1.8的默认垃圾回收器,除了多线程垃圾回收过程之外其他和Serial垃圾回收器一致。可以通过参数 -XX:ParallelGCThread指定垃圾回收线程数,但是一般不建议修改。Parallel垃圾回收器关注的是吞吐量,也就是应用运行时间和CPU总运行时间之比,而CMS则是更加关注减少应用线程停顿时间,提高用户体验。
年轻代使用的是标记复制法,老年代使用的是标记整理法。
并不是说垃圾回收线程越多就越好,对于CPU核数比较少的机器,选择Serial反而会更好,因为Parallel会有线程切换的开销,而且CPU核数少Parallel回收器也没有用武之地。
ParNew
该垃圾回收器通过参数 -XX:+UseParNewGC开启。
该垃圾回收器和Parallel垃圾回收器很像,区别在于除了Serial垃圾回收器外只有它能够和CMS垃圾回收器搭配使用。
CMS
该垃圾回收器通过参数 -XX:+useConcMarkSweepGC开启。
CMS垃圾回收器是真正意义上以获取最短应用线程停顿时间为目标的垃圾回收器,真正意义上达到了和用户线程并行。可以通过参数 -XX:ConcGCThreads指定并发的GC线程数。
这种回收器应用在老年代中使用的是标记清除法。
这种垃圾回收器比较复杂,包含了五个回收过程。
1.初始标记:可通过参数 -XX: CMSParallelInitialMarkEnabled设定多线程执行。STW,会标记GC Root直接能引用的对象,速度很快。
2.并发标记:从GC Root开始遍历整个对象图,整个耗费时间较长,但是不需要停顿用户线程。由于用户线程一直在运行,可能会导致已经发生过标记的对象状态发生改变。
3.重新标记:可通过参数 -XX: CMSParallelRemarkEnabled设定多线程执行。STW,修正在并发标记过程中由于用户线程继续运行导致的对象回收状态发生变化的标记记录,该过程消耗时间较长,但是还是比并发标记要短。
4.并发清除:开启用户线程,GC线程对未标记的对象进行清理。
5.并发重置:重置本次GC过程的对象标记。
CMS会和应用线程抢资源,并且由于是标记清除法,会产生大量的空间碎片,可以使用一些参数在清除后进行整理。-XX:UseCMSCompactAtFullCollection可以开启在Full GC后压缩碎片进行整理,而参数 -XX:CMSFullGCsBrforeCompaction则可以指定在多少次Full GC之后进行整理,默认值为0,代表每一次Full GC后都进行整理。
CMS垃圾回收器并不是说把所有的空间用完了才进行Full GC,默认情况下是92%,如果需要手动设定可以使用参数 -XX:CMSInitiatingOccupanyFraction设定达到多少比例后进行Full GC,如果没有设定参数 -XX:+UseCMSInitiatingOccupanyOnly会在第一次回收时使用设定值,后续会进行动态的调整,如果设定了只会达到设定值才进行GC。
还可以开启参数 -XX:+CMSScavengBeforeRemark在Full GC前先执行Minor GC,减少老年代对年轻代1的引用,有助于减少CMS的标记时间。
如果CMS在上一次垃圾回收还没有完成,又在并发阶段触发了垃圾回收的话,会触发"concurrent mode failure",触发后进入STW,切换为Serial Old进行回收。
总结
本节介绍三种垃圾回收算法,分别为标记复制,清除和整理。同时也介绍了常用的几种垃圾回收器,其中CMS垃圾回收器最为复杂。下一节将继续介绍更加复杂的垃圾回收器,分别为G1和ZGC,本节还包括了一些常用的JVM参数,这些对于我们JVM调优都是有帮助的。
感谢观看!