Java性能权威指南-总结9

Java性能权威指南-总结9

  • 垃圾收集算法
    • 理解CMS收集器
      • CMS收集器的永久代调优
      • 增量式CMS垃圾收集

垃圾收集算法

理解CMS收集器

CMS收集器的永久代调优

从CMS垃圾收集日志中发现,如果永久代需要进行垃圾收集,就会发生FullGC(如果元空间的大小需要调整也会发生同样的情况)。这往往发生在程序员频繁部署(或者重新部署)应用的服务器上,或者发生在需要频繁定义(或者回收)类的应用中。

默认情况下,Java7中的CMS垃圾收集线程不会处理永久代中的垃圾,如果永久代空间用尽,CMS会发起一次Full GC来回收其中的垃圾对象。除此之外,还可以开启-XX:+CMSPermGenSweepingEnabled标志(默认情况下,该标志的值为false),开启后,永久代中的垃圾使用与老年代同样的方式进行垃圾收集:通过一组后台线程并发地回收永久代中的垃圾对象。注意,触发永久代垃圾回收的指标与老年代的指标是相互独立的。 使用-XX:CMSInitiatingPermOccupancyFraction=N参数可以指定CMS收集器在永久代空间占用比达到设定值时启动永久代垃圾回收线程,这个参数的默认值为80%。

不过,开启永久代垃圾收集只是整个流程中的一步,为了真正释放不再被引用的类,还需要设置-XX:+CMSClassUnloadingEnabled标志。否则,即使启用了永久代垃圾回收也只能释放少量的无效对象,类的元数据并不会被释放。由于永久代中大量的数据都是类的元数据,因此启动CMS永久代垃圾收集时,这个标志同时也应该开启。

Java8中,CMS收集器默认就会收集元空间中不再载入的类。如果由于某些原因,希望关闭这一功能,可以通过-XX:-CMSClassUnloadingEnabled标志进行关闭(默认情况下这个标志是开启的,即该值为true)。

增量式CMS垃圾收集

为了进行有效的CMS垃圾收集,需要消耗额外的CPU处理资源。如果只有一个单CPU的机器,或者有多个非常忙碌的CPU,但是希望使用低延迟的垃圾收集器,这时有什么好的建议呢?

增量式CMS垃圾收集在Java8中已经不推荐使用
增量式CMS垃圾收集(iCMS)在Java8中已经不推荐使用了,不过暂时还保留在其中,但是在Java9中很可能会被移除。
使用增量式CMS垃圾收集的主要好处是后台线程会间歇性地停顿,让出一部分CPU给应用程序线程运行,从而使得CMS收集器即使在只配备了有限CPU资源的机器上也能运行。
随着多核技术的发展,多处理器几乎已经成为所有系统的标准配置(连我的手机都装载了4核的CPU芯片),这使得iCMS存在的意义变得不再那么重要。
如果系统确实只配备了极其有限的CPU,作为替代方案,可以考虑使用G1收集器——因为G1收集器的后台线程在垃圾收集的过程中也会周期性地暂停,
客观上减少了与应用线程竞争CPU资源的情况。

这些情况下,使用CMS收集器进行增量式的垃圾收集,即只要有后台线程运行(同一个时刻处于运行状态的线程数不应该超过一个),垃圾收集器就不会马上对整个堆进行垃圾收集。这个后台线程间断性地暂停,有助于整个系统吞吐量的提高,因为更多的CPU处理资源让给了应用线程的运行。当然,如果CMS收集线程一旦运行起来,还是会与应用程序线程争夺有限的CPU处理周期。

指定-XX:+CMSIncrementalMode标志可以开启增量式CMS垃圾收集。通过改变标志-XX:CMSIncrementalSafetyFactor=N-XX:CMSIncrementalDutyCycleMin=N-XX:CMSIncrementalPacing可以控制垃圾收集后台线程为应用程序线程让出多少CPU周期。

增量式CMS垃圾收集依据责任周期(duty cycle)原则进行工作,这个原则决定了CMS垃圾收集器的后台线程在释放CPU周期给应用线程之前,每隔多长时间扫描一次堆。从操作系统的层次上看,CMS垃圾收集器的后台线程已经和应用的线程发生了竞争(通常是基于时间片的)。换个角度看,这些标志实际控制着主动暂停运行、释放资源给应用线程运行之前,后台线程持续运行的时间。

责任周期的时间长度是以新生代相邻两次垃圾收集之间的时间长度计算得出的;默认情况下,增量式CMS垃圾收集持续的时间是该时长的20%左右(至少初始时是这个值,不过CMS会不断调整该值以适应不断晋升到老年代的对象数目)。如果这个时间不够长,就会发生并发模式失效(以及Full GC)。目标就是通过调整增量式CMS垃圾收集,避免发生这种GC(或者尽量减少它们发生的频率)。

可以从调整增大CMSIncrementalSafetyFactor参数入手,这个参数设置是增加到默认责任周期的时间百分比。责任周期的默认值是10%,默认情况下,安全因子(safety factor)的值是再增加10%(这样默认的初始责任周期所占用的时间百分比就变成了20%)。通过增大安全因子(最大可以增加到90,不过这会导致增量周期占用所有的时间),可以让后台线程有更多的运行时间。

除此之外,如果参数CMSIncrementalDutyCycleMin设置得比默认值(10)更大也可以调整责任周期的长度。不过这个参数值会受JVM自动调节机制的影响,因为JVM的自动调节机制会监控由新生代晋升到老年代的对象数并进行相应的调节。所以,即使增大这个值,JVM可能还是会依据自身的判断,即增量式垃圾收集运行不需要运行得过于频繁,而减小这个参数的值。如果应用程序运行时操作有爆发式的波峰,通过自动调节机制计算出的结果通常不准确,需要显式地设置责任周期,同时调整CNSIncrementalDutyCycle标志关闭自动参数调节(CMSIncrementalDutyCycle的值默认为真,即开启)。

快速小结

  1. 应用在CPU资源受限的机器上运行,同时又要求较小的停顿,这时使用增量式CMS收集器是一个不错的选择。
  2. 通过责任周期可以调整增量式CMS收集器;增加责任周期的运行时间可以避免CMS收集器发生并发模式失效。

你可能感兴趣的:(java,jvm,算法)