(10)美团GC优化实际案例(2)

Java程序性能不达标,其他优化穷尽时,调整垃圾回收器来提高性能。但GC算法复杂,影响性能参数多。概要:

优化步骤:   明确目标→优化→跟踪结果。 案例 1、调eden大量短期对象,用较大年轻代

2、强制在Remark前gc,减少整体时间   3、固定Perm容量,避免扩容,发生full gc

一、优化方法

 1、设置比例(例) 

(10)美团GC优化实际案例(2)_第1张图片

例如,GC日志获得老年代的活跃数据大小为300MB,那么各分区大小

(10)美团GC优化实际案例(2)_第2张图片

2、确定目标

假设单位时间T内发生一次持续25msGC,接口平均响应时间50ms,且请求均匀到达

(10)美团GC优化实际案例(2)_第3张图片

(50ms+25ms)/T请求受GC影响:GC前50ms内到达请求增加25ms,GC期间25ms内到达请求增加0-25ms不等,如发生N次GC,受GC影响请求占比=(接口响应时间+GC时间)×N/T 。可见无论降低单次GC时间 还是次数N都减少GC对响应时间影响

3、优化

如选回收器、设置内存比例调整JVM参数

本文案例垃圾回收器均为ParNew+CMS,CMS失败时Serial Old替补

案例一:Major GC和Minor GC频繁

Minor GC  100次/分,单次25ms,Major GC  4分钟一次,单次200ms,接口响应时间50ms

服务要求低延时高可用,Minor GC,12.5%请求响应时间会增加,其中8.3%的请求响应时间会增加25ms,影响较大。(50ms+25ms)× 100次/60000ms = 12.5%,50ms × 100次/60000ms = 8.3% 。

优化目标:降低TP99、TP90时间

1、优化

(1)Eden大小:Minor GC频繁:通常由于新生代空间小,Eden区很快被填满,Eden增加一倍,Minor GC减少一半。但单次Minor GC时间可能增加

    单次时间两部分:T1(扫描新生代)和 T2(复制存活对象到Survivor区)

(10)美团GC优化实际案例(2)_第4张图片

(2) 1)扩容前:新生代容量为R ,对象存活时间为750ms,Minor GC间隔500ms,本次GC时间= T1(扫描新生代R)+T2(复制对象A到S)。

      2)扩容后:容量为2R ,GC间隔1000ms,A不再存活,不需复制,时间 = 2 × T1(扫新生代R),没有时间。

复制成本远高于扫描,单次Minor GC时间取决于GC后存活对象数量,非Eden区大小。如堆中短期对象多,扩容新生代,单次Minor GC时间不会显著增加。下面要确认服务中对象的生命周期分布

(10)美团GC优化实际案例(2)_第5张图片

(3)上图GC日志中红色可知:

    1)new threshold = 2:2次Minor GC后就晋升到老年代,老年代满,频繁Major GC

    2)GC后老年代空间300Mb+,意味大多数(86% = 2G/2.3G)不活,生命周期长的对象

2、优化结果

新生代为原来三倍,单次Minor GC时间增加小于5ms频率下降60%,服务响应时间TP90,TP99都下降了10ms+

小结:大量短期对象,用较大年轻代;较多持久对象,老年代适当增大。

(10)美团GC优化实际案例(2)_第6张图片
调整前
(10)美团GC优化实际案例(2)_第7张图片
调整后

3、动态年龄计算

MaxTenuringThreshold=15,对象仍2次Minor GC,就到老年代?

Hotspot遍历所有对象时,年龄超过survivor区一半时,取年龄MaxTenuringThreshold中更小值,作为晋升年龄阈值。本例中,调优前:Survivor = 64M,desired survivor = 32M(目标使用率),age<=2为41M,大于32M,所以设置为2。

引入原因动态适应变化:

    a)MaxTenuringThreshold过大,应该晋升在Survivor区,Survivor区溢出,Eden+Svuvivor全部到老年代,老化机制失效

    b)MaxTenuringThreshold过小,大量短对象到老年代,频繁Major GC分代回收失去意义

案例二:请求高峰期发生GC,导致服务可用性下降

确定目标:降低Remark时间,高峰期CMS在重标记(Remark)阶段耗时1.39s。Remark阶段Stop-The-World,

(10)美团GC优化实际案例(2)_第8张图片

1、优化

(10)美团GC优化实际案例(2)_第9张图片
(10)美团GC优化实际案例(2)_第10张图片

1、Remark阶段仅扫描老年代是否可行?不可行

    跨代引用:对象A因为引用在新生代,Remark时不会被修正标记为可达,GC时会被错误回收

    Remark阶段必须扫描整个堆来判断对象是否存活,包括图中灰色的不可达对象。

分析GC日志得出,Remark耗时>500ms时,新生代使用率在75%以上。

2、减少新生代对象数量

1)CMS在Remark前增加可中断并发预清理(CMS-concurrent-abortable-preclean),Eden区使用超过2Mb启动,使用率50%中断(2Mb、50%默认阈值),此阶段执行时Minor GC灰色对象将被回收,Remark扫描就少

2)避免没到Minor GC陷入无限等待,CMSMaxAbortablePrecleanTime = 5s,不管发没发生Minor GC,都中止此阶段,进入Remark。

    例:红色标记2处,并发预清理5.35s,被中断,没等到Minor GC ,Remark时新生代中仍很多对象。

3)对于这种情况,CMSScavengeBeforeRemark参数,Remark前强制一次Minor GC

优化结果:GCtime和业务波动保持一致,不再有明显的毛刺。

(10)美团GC优化实际案例(2)_第11张图片

3、如何避免Minor GC扫描全堆?

Minor GC时必须扫老年代,因为老年代可能有新生代引用。

老持新情况不足1%,JVM引入了卡表(card table)老年代空间分成若干个512B卡(单字节数组),老引新时,设置卡表元素。之后Minor GC扫描很快识别

(10)美团GC优化实际案例(2)_第12张图片

主案例三:发生Stop-The-World的GC

目标:Full GC耗时1.23s,降低单次STW时间

(10)美团GC优化实际案例(2)_第13张图片

1、触发STW的Full GC原因:

    1)Perm空间不足(JDK8开始,Perm完全消失,用元空间,直接存内存中,不在JVM)

    2)  promotion failedconcurrent mode failure发生原因一般是CMS进行,但老年代不足,尽快回收老年代,STW,进行Serial Old GC

    3)  新晋升到老年代大小>老年代剩余空间

    4)触发Full GC(执行jmap -histo:live [pid])避免碎片

排除原因2:原因2中两种情况,日志中会有特殊标识(当前没有)。

排除原因3:根据GC日志,老年代仅用20%,没有大于2G对象

排除原因4:没执行命令

2、锁定原因1,两种解决方法:

    1)-XX:PermSize和-XX:MaxPermSize设置成一样,启动强制 固定永久带容量,减少内存自动扩容和收缩性能损失

    2)CMS默认不会回收Perm区,通过CMSPermGenSweepingEnabled、CMSClassUnloadingEnabled ,让CMS在Perm区容量不足时对其回收

由于没生成大量动态类,回收Perm区收益不大,用方案1,Perm区大小固定,避免进行动态扩容。

https://mp.weixin.qq.com/s/t1Cx1n6irN1RWG8HQyHU2w

你可能感兴趣的:((10)美团GC优化实际案例(2))