Java教程:JVM相关问题整理(十)

开课吧开课吧锤锤2021-03-12 13:44

    Java编程语言是一种简单、面向对象、分布式、解释型、健壮安全、与系统无关、可移植、高性能、多线程和动态的语言。如今Java已经广泛应用于各个领域的编程开发。

Java

    补充问题:

    G1和CMS的比较

    CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。

    CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。

    CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。

    G1是一款面向服务端应用的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STW的停顿时间,它满足短时间停顿的同时达到一个高的吞吐量。

    从JDK9开始,G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:FullGC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。

    G1将空间划分成很多块(Region),然后他们各自进行回收。堆比较大的时候可以采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。

    G1需要记忆集(具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在G1中需要占用大量的内存,可能达到整个堆内存容量的20%甚至更多。而且G1中维护记忆集的成本较高,带来了更高的执行负载,影响效率。所以CMS在小内存应用上的表现要优于G1,而大内存应用上G1更有优势,大小内存的界限是6GB到8GB。

    CMS垃圾回收器存在的问题及解决方案

    CMS是使用标记-清理算法去垃圾回收的。其中四个主要的流程分别是初始标记、并发标记、重新标记、并发清理。

    并发消耗CPU资源

    其中的并发标记和并发清理是工作线程和垃圾回收线程并发工作,这样在需要STW的时间内不会让整个系统不可用。但是在并发标记阶段,需要根据GCRoots标记出大量的存活对象,而在并发清理阶段,则需要将垃圾对象从各种随机内存位置删掉,这两个阶段都非常消耗性能,所以垃圾回收线程会占用一部分的CPU资源,导致系统的执行效率降低。

    CMS默认的回收线程数是(CPU个数+3)/4,当在CPU核数较多的时候,对系统性能的影响并不是特别大。但是如果是CPU核数较少,例如双核的时候,就会占用一个CPU去处理垃圾回收,系统的CPU资源直接降低50%,这就严重影响了效率。

    因为现在CPU的核数越来越多,所以这种场景基本不会对系统造成很大的影响,可以忽略不计。

    ConcurrentModeFailure问题

    并发清理阶段,工作线程和垃圾回收线程并发工作的时候,此时工作线程会不断产生新的垃圾,但是垃圾回收线程并不会去处理这些新生成的垃圾对象,需要等到下次垃圾回收的时候才会去处理,这些垃圾对象称之为:浮动垃圾。因为有这些浮动垃圾的存在,所以老年代不能在100%使用的时候才去进行垃圾回收,否则就放不下这些浮动垃圾了。

    有一个参数是“-XX:CMSInitiatingOccupancyFraction”,这个参数在jdk1.6里面默认是92%,意思是老年代使用了92%的空间就会执行垃圾回收了。但是即使预留了8%的内存去存放浮动垃圾,但是还是有可能放不下,这样就会产生ConcurrentModeFailure问题。一旦产生了ConcurrentModeFailure问题,系统会直接使用SerialOld垃圾回收器取代CMS垃圾回收器,从头开始进行GCRoots追踪对象,并清理垃圾,这样会导致整个垃圾回收的时间变得更长。

    解决办法就是根据系统的需求,合理设置“-XX:CMSInitiatingOccupancyFraction”的值,如果过大,则会产生ConcurrentModeFailure问题,如果设置的过小,则会导致老年代更加频繁的垃圾回收。

    空间碎片问题

    CMS的标记-清理算法会在并发清理的阶段产生大量的内存碎片,如果不整理的话,则会有大量不连续的内存空间存在,无法放入一些进入老年代的大对象,导致老年代频繁垃圾回收。所以CMS存在一个默认的参数“-XX:+UseCMSCompactAtFullCollection”,意思是在FullGC之后再次STW,停止工作线程,整理内存空间,将存活的对象移到一边。还要一个参数是“-XX:+CMSFullGCsBeforeCompaction”,表示在进行多少次FullGC之后进行内存碎片整理,默认为0,即每次FullGC之后都进行内存碎片整理。

    CMS虽然使用并发的方式降低了STW的时间,但是还需要配合一些CMS的参数才能完全发挥出CMS的优势,否则甚至会降低垃圾回收的效率。因此只有掌握了CMS的原理和参数的调试,才能让系统运行的更加流畅。

    以上内容由开课吧老师张彦峰ZYF提供,更多Java教程尽在开课吧广场Java教程频道。

有用
分享