java性能调优权威指南读书笔记七(延迟调优)

延迟调优

这一步调优的目的是达到程序的延迟性需求,其中的手段有优化java堆的大小的配置,不同垃圾收集器的切换

在这里我们的延迟调优指的是最大延迟时间,所以以这个标准为目的我们在调优的时候需要减少每次垃圾收集的时间,这就需要我们的垃圾收集需要使用高次数低停顿的策略

所以我们会在一下的几个活动中进行对于垃圾收集器的延迟影响的评估

测量MinorGC的持续时间

通知MinorGC的次数

测量FULL GC的最长时间

最差情况下FULL GC的频率

在这种的调优下,我们需要关注在需求中我们可以有操作空间的四个部分,可接受的平均停滞时间,可接受的Minor GC频率,可接受的最大停顿时间,最大停顿的发生频率

可以通过以下的几个方式来进行调优

优化新生代的大小

在这个方面是可以视应用的情况而定,因为新生代的垃圾收集方式是stop the world所以这里也会造成应用的全面停顿,所以如果由于Minor GC的停顿过长我们需要调小新生代的空间,如果是停顿的次数过多那么我们需要调大这个空间

并且在新生代调整的时候有以下几个准则1老年代空间大小不应小于活跃数据的1.5倍2新生代空间至少为java堆大小的10%3不能超过java虚拟机可用的内存大小

优化老年代的大小

这一步的目标是评估Full GC引入的最差停滞时间以及Full GC的频率

和新生代一致的是在这里我们也需要获取垃圾收集的数据只不过改成了Full GC的垃圾收集数据,最长停滞时间,平均时间和频率

而我们可以通过监控Minor GC的提升情况,以及每次Minor GC之后老年代大小变化的数量来判断每次Minor GC从新生代晋升到老年代的数据数量,并根据频率可以计算出大概多少时间老年代就会进入full GC

如果发现频率过高可以调大老年带的大小,但是随之而来的就是最差性能的FULL GC将会有更大的延迟;反之可以通过减小老年代的大小来讲最差性能的FULL GC的延迟减低,但是代价是更多的full Gc次数

如果在调整老年代大小之后只发生full gc一般是由于老年代在FULL gc之后的剩余空间不足以满足晋升的对象,这个时候需要适当增加老年代的大小

 

CMS调优延迟

Cms模式的垃圾收集由于其并行的方法,所以能在很大程度上降低由于老年代垃圾收集的停顿时间,但CMS并不进行内存的压缩,所以这个效果主要是通过避免老年代空间发生Stop-the-World压缩式的垃圾收集来实现的,一旦老年代发生溢出就会触发Stop-the-World压缩式的垃圾收集

CMS收集器的调优是相对比较精细的内容,其中涉及到了新生代空间的大小以及何时启动老年代的垃圾收集。相对于基础的Throughput垃圾收集器,cms在发生新生代对象提升时消耗的时间会更长,这是由于cms没有进行内容的压缩,另外由于老年代垃圾收集线程能够与应用线程实现最大程度的并发执行,所以可以预期应用程序的吞吐量会变得更低。

当老年代的空间用尽就会触发一个单线程的stop-the-world压缩式的垃圾收集,这将会造成引用长时间无法响应,所以如果我们的应用需要从Throughput垃圾收集器迁移到cms垃圾收集器的时候需要将老年代的空间增大20%到30%

CMS收集器的调优由于一下的几个关注点变得特别的复杂

1 对象从新生代提升至老年代的速录

2 并行老年代回收空间的速率

3由于老年代空间的碎片化问题(通过减少提升到老年代空间的对象进行处理)

Survivor空间以及晋升阈值的调优

Survivor空间是新生代空间的一部分,如果有疑问需参考之前的笔记内容,基本上就是将新生代空间分成了一个Eden以及两个Survivor,然后只在Eden空间生成新的对象,并随着minor GC使得幸存的对象在两个Survivor之间进行来回的移动。

但是在minor GC的时候有时候会出现这样的情况,从一个Survivor幸存的对象加上Eden幸存的对象大于了还有一个Survivor的空间大小,这个时候会导致溢出的部分对象直接被提升到老年代空间中去,在我们延迟性调优的过程中这个我们需要极力的避免,所以我们需要适当的增加Survivor空间的大小,避免不必要的老年代内存提升。

具体可以通过参数-XX: SurvivorRatio=?进行调整,这里表示的是Eden空间和Survivor空间的大小比值,所以在调整的过程中Eden的空间会变小。

其他的我们还需要通过晋升的阈值来控制新生代对象提升到老年代的速度,晋升阈值,顾名思义是我们是否需要提升一个对象到老年代的判断标准,当一个对象新被分配出来时他的年龄是0,随着经历一次minor GC对象的年龄就会增加1,直到对象的年龄大于阈值之后这个对象会被提升到老年代中去

如果这个阈值过大,将会造成过多的对象积压在新生代的一块Survivor中,一旦发生溢出,会造成全部对象提升到老年代,这将对于内存对象的年龄管理造成冲突,无法发挥将堆内存划分为新老年代区域的优势,从而导致应用的性能体现下降

当这个阈值过大时,将会有过多的对象提升到老年代中,增加了老年代GC的压力,从而同样会导致应用的性能下降

综上我们需要通过这个阈值来达到新老对象的合理划分,使得合理数量的对象留在新生代,而相对比较持久的对象提升到老年代

  最大晋升值可以通过JVM参数-XX:MaxTenuringThreshold= ?进行设置,但是一般来说当前晋升的阈值是由JVM计算出来的,并且不会超过我们设置的最大值

  在两个参数中我们需要通过调节两者使得我们尽量少的内存被提升到老年代中。

初始化CMS垃圾收集周期

这个是由于CMS和应用是出于并行状态下的,在CMS垃圾收集的过程中依旧会有部分对象被提升到老年代中来,如果在这个过程中发成老年代空间不足,那么就会直接触发stop-the-world压缩式的垃圾收集,而这是我们最不希望看到的结果;其次如果是CMS启动的过早就会运行的太频繁消耗过多的系统资源,降低了应用的吞吐量,但是一般来说前者更加糟糕一些。

在调优这个项目的时候关键信息是并发模式失效(Concurrent ModeFailure),而我们可以通过JVM参数-XX:CMSInitiatingOccupancyFraction=?这个参数是设定CMS在老年代空间占比到达多少时触发垃圾收集

显式垃圾收集

在应用中可以通过System.gc()进行垃圾收集,可以通过JVM参数进行调整,但一般在有明确理由的时候才进行这么的处理,一般是不太建议

并发永久带垃圾收集

Full的调用可能源于永久带的空间用尽,在进行调优的时候还需要注意永久带的空间情况。可以通过一个HotSpot VM的参数开启永久带垃圾收集-XX:+CMSClassUnloadingEnabled,同样有参数可以控制在永久带空间占用多少百分比的时候触发永久带垃圾收集

 

调优CMS停顿时间

CMS虽然是并行与应用的,但是还是需要有两个stopworld的时间,分别是初始标记阶段和重新标记阶段。虽然初始标记的阶段是单线程的,却占用的时间很少,而重新标记阶段是多线程的却会照用比较多的时间,我们可以通过一个JVM参数-XX:ParallelGCThreads=?来进行重新标记阶段的线程数量的控制。

你可能感兴趣的:(开卷有益,jvm,性能优化)