上期我们讲到LoadRunner性能测垃圾收集算法,这期我们讲LoadRunner性能测试垃圾回收器。
垃圾回收器
前面介绍了回收的算法,但真正进行回收的是实现这些算法的收集器,JVM垃圾回收器一共有七种,其中年轻代收集器有三种:Serial、ParNew、Parallel Scavenge年老代收集器也有三种:CMS、Serial Old、Parallel Old对整个堆有效的收集器为G1收集器,如图所示。
1)Serial收集器 Serial收集器,也叫串行收集器,它是最基本的、发展历史最悠久的收集器,是单线程收集器,但这个单线程并不说只能是一个CPU或一条收集线程去完成垃圾收集工作,Serial收集器在收集垃圾时,必须暂停其他所有工作线程直到回收结束,如图所示。
Serial收集器优点:该收集器简单高效,因为采用的是单线程的方法,因此与其他类型的收集器相比,对单个CPU来说没有了上下文之间的的切换,由于没有线程交互的开销,专心垃圾收集自然可以获得最高的单线程效率。。 缺点:会停止所有在工作线程。 适用场景:Client模式(桌面应用);单核服务器。 参数:可以以下参考设置来开启Serial作为新生代收集器 -XX:+UserSerialGC#选择Serial作为新生代垃圾收集器
2)ParNew收集器 ParNew收集器是在Serial收集器上进行优化的,主要优化的是在新生代阶段收集时不再是单线程收集,而是多线程收集,但在年老代还是使用单线程进行收集。ParNew收集器在每个阶段收集的收集算法与Serial收集器的算法一致。 ParNew收集器工作原理,如图所示。
ParNew收集器是大部分运行在Server模式下虚拟机的首选,因为除Serial收集器外,目前也只能ParNew收集器可以与CMS收集器配合工作。CMS收集器是一个很重要的并发收集器,在后面会有详细的介绍。ParNew收集器可以设置最大可以同时使用的线程数量,如果是单个CPU的情况下,ParNew收集器并不会比Serail收集器更好的效果,因为线程交互需要开销。 ParNew收集器设置的相关参数如下: 如果指定使用CMS,会默认使用ParNew作为新生代收集: "-XX:+UseConcMarkSweepGC" 强制设置使用ParNew "-XX:+UseParNewGC" 设置ParNew开启的收集线程数,默认是与CPU数量相同 "-XX:ParallelGCThreads"
3)ParallelScavenge收集器 ParallelScavenge收集器是用于新生代的一种收集器,收集时也是使用复制算法,并且也是多线程收集器,这与ParNew收集器很相似。只是ParallelScavenge收集器更关注吞吐量, 吞吐量是指CPU中用于运行用户代码的时间与CPU总消耗时间的比值。(吞吐量=CPU用于用户代码的时间/CPU总消耗时间的比值,即等于运行用户代码的时间/(运行用户代码时间+垃圾收集时间)。 ParallelScavenge收集器工作原理如图所示。
ParallelScavenge收集器有两个参数可以用于精确控制吞吐量:最大垃圾收集停顿时间和垃圾收集时间占总时间的比率两个参数。 最大垃圾收集停顿时间参数设置如下: "-XX:MaxGCPauseMillis" 最大垃圾收集停顿时间单位是毫秒,值一般大于0,MaxGCPauseMillis不能设置太小,如果设置的太小,那么停顿的时间就会很缩短,这样可能会使吞吐量下降,因为时间过短可能会导致垃圾收集的次数会更频繁。 垃圾收集时间占总时间的比率参数设置如下: "-XX:GCTimeRatio" 垃圾收集时间占总时间的比率为大于0小于100的整数。GCTimeRatio参数相当于设置吞吐量大小。
垃圾收集执行时间占应用程序执行时间的比例的计算方法是:1/(1+n)。例如,选项-XX:GCTimeRatio=39,设置了垃圾收集时间占总时间的25%=1/(1+39);默认值是1%,即n的值为99。
与ParNew收集器不同的是ParallelScavenge收集器还有可以设置GC自适应调节策略,其设置参数如下: "-XX:+UseAdptiveSizePolicy" 设置这个参数后,就不用手工指定一些参数了,如以下参数会自动调整。 不用再设置新生代的大小中Eden与Survivor区的比例、晋升老年代的对象年龄等; JVM还会根据当前系统运行情况收集性能监控信息,动态调整这些参数,以确定最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GCErgonomics);
4)SerialOld收集器 SerialOld收集器是Serial收集器的老年代版本,前面介绍的是年轻代的Serial收集器。其实工作原理见图所示。SerialOld收集器也是单线程收集器,采用的是标记-整理算法。主要也使用在Client模式下的虚拟机中,但也可在Server模式下使用。
在Server模式下可以与ParallelScavenge收集器搭配使用,也可以作为CMS收集器的后备方案,在并发收集ConcurentModeFailure时使用。
5)ParallelOld收集器 ParallelOld收集器是ParallelScavenge收集器针对老年代收集的一个版本,是多线程处理,使用“标记-整理”算法,其工作流程见图所示。 其设置参数如下: 设置使用ParallelOld收集器: "-XX:+UseParallelOldGC"
6)CMS(ConcurrentMarkSweep)收集器 CMS(ConcurrentMarkSweep)收集器是通过一种算法来获取最短回收停顿时间为目标的收集器。CMS收集器是使用的是“标记-清除”的算法,其实工作主要分以下四个步骤来完成。
第一步:暂停所有用户线程,初始标记GCRoots可以直接到达的对象。
第二步:并发标记,同时开启GC线程和用户线程,用一个闭包结构去记录可达对象。但即使这个阶段结束后,这个闭包结构也不一定能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方;
第三步:重新标记,重新标记的目的是修正并发标记阶段因为用户线程而导致标记对象不完全正确的情况重新标记需要“StopTheWorld”这个停顿时间比初始标记会长一些,但会比并发标记的时间短很多。
第四步:并发清除,与用户线程同时进行,GC线程开始对为标记的区域中的对象进行清除,回收所有的垃圾对象; CMS(ConcurrentMarkSweep)收集器工作过程如图所示。
CMS采集器参数设置如下: 设置使用CMS收集器 "-XX:+UseConcMarkSweepGC"
CMS收集器会产生以下弊端: 对CPU资源要求会更高 CMS收集器虽然不会导致所有用户线程都停顿,但是会因为用户线程占用了CPU资源,从而导致应用程序变量并且总吞吐量会降低。
CMS的默认收集线程数量=(CPU数量+3)/4;当CPU数量越多,回收的线程占用CPU就少。例如,CPU在5个时,并发回收时垃圾收集线程为25%的CPU资源;当CPU不足5个时,影响更大,可能无法接受。比如CPU为2时或者更小时,那么就启动一个线程回收,占了50%以上的的CPU资源。回收线程在工作过程中会一直占用CPU资源。
无法处理浮动垃圾 当并发清除时,用户线程可能会产生新的垃圾,这类垃圾称之为浮动垃圾,如果浮动垃圾无法处理,那么就会出现“ConcurrentModeFailure”的错误信息。
如果出现上述错误,说明老年代预留的内存空间不勉励,那么就需要将预留的空间设置的大一些,可以使用“-XX:CMSInitiatingOccupancyFraction”设置CMS预留老年代内存空间。 产生大量内存碎片
由于CMS对年老代使用的是“标记+清除”算法来回收对象,因此长时间运行后会产生大量的内存空间碎片,这样可能会导致新生代对象晋升到老年代时失败。当碎片过多时,就会给大对象的分配内存时带来问题。如果无法找到连续的内存空间来就不得不提前触发FullGC回收。
为了解决这个问题,可以通过“-XX:+UseCMSCompactAtFullCollection”“-XX:+CMSFullGCsBeforeCompaction”两个参数来调整。 UseCMSCompactAtFullCollection参数设置如下: "-XX:+UseCMSCompactAtFullCollection" 这个参数是一个标记,如果出现这个标记时,将不进行FullGC,而是开启内存碎片的合并整理过程。默认值这个参数是开启的,但不会进行,需要结合CMSFullGCsBeforeCompaction参数一块使用。
CMSFullGCsBeforeCompaction参数设置如下: −XX:CMSFullGCsBeforeCompaction 由于合并整理是无法并发执行的,空间碎片问题没有了,但是会导致连续的停顿。因此,可以使用这个参数一块使用,表示当多少次不压缩的FullGC之后,对空间碎片进行压缩整理。这样可以减少合并整理过程中的停顿时间,这个参数的默认值为0,即每次都执行FullGC不会进行压缩整理。 7)G1收集器 G1(Garbage-First)是JDK7-u4才推出商用的收集器,是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。
G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。
G1收集器有以下优点: 并行与并发 G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
分代收集 G1能够独自管理整个Java堆,并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
空间整合 G1运作期间不会产生空间碎片,收集后能提供规整的可用内存。 可预测的停顿 G1除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒