JVM老年代GC调优 原创 精华

公众号JavaEdge
发布于 2022-1-23 23:40
浏览
0收藏

春节不停更,此文正在参加「星光计划-春节更帖活动」

在新生代优化好的背景下,何时会让一些对象进入老年代?

  • -XX:MaxTenuringThreshold=5 让在1、2min内连续躲过5次Minor GC对象迅速进入老年代

    一般就是@Service、@Controller之类的注解标注的那种系统业务逻辑组件,这种对象实例一般全局单实例,一直使用,所以一般会长期被GC Roots引用,这种对象一般不会太多,大概最多一个系统就几十MB

    JVM老年代GC调优-鸿蒙开发者社区

  • 按JVM参数,若分配一个超过1MB大对象,如创建大数组或大List,直接进入老年代

    这种大对象我们假设在这个案例里无,所以忽略不计。

  • Minor GC后,可能存活的对象超过200MB,Survivor放不下或是一下子占到超过Survivor的50%,此时会有一些对象进入老年代

    但之前对新生代的JVM参数进行优化,就是为避免这种情况,所以这种概率很低。虽说很低,也不是完全没可能,比如某次GC后,可能刚好机缘巧合有超过200MB的对象,就会进入老年代。

    假设就是订单系统在大促期,每隔5min会在Minor GC后有一小批对象进入老年代, 大概200MB:

    JVM老年代GC调优-鸿蒙开发者社区

大促期多久触发一次Full GC?

Full GC触发条件

1 没打开 -XX:HandlePromotionFailure 选项

结果老年代可用内存最多就1G,新生代对象总大小最多可有1.8G。造成每次Minor GC前,检查都满足:

老年代可用内存 < 新生代总对象大小

导致每次Minor GC前都触发Full GC。现在JDK 1.6后版本废弃该参数,所以只要满足下面这第2个条件就能直接触发Minor GC,而无需触发Full GC。

2 Minor GC前,检查 老年代可用内存空间<历次Minor GC后升入老年代的平均对象大小

按目前设定,要很多次Minor GC后,才可能有一两次恰有200MB升入老年代,所以这“历次Minor GC后升入老年代的平均对象大小”数值基本很小。

3 可能某次Minor GC后要升入老年代的对象有几百MB,但老年代可用空间不足

4 设置了“-XX:CMSInitiatingOccupancyFaction”参数

如设定为92%,则此时可能前面几个条件都不满足,但刚好满足这条件:老年代空间使用超过92%,就会自行触发Full GC。

实际系统运行期间,可能慢慢有对象进入老年代,但因新生代已经优化过内存分配,所以对象进入老年代的速度很慢。所以可能系统运行 0.5~1h后,才有接近1G对象进入老年代。此时可能因为上述条件2、3、4任一满足,就会触发Full GC。但这三条件一般都需老年代近乎占满时才可能触发。

假设大促期,订单系统运行1h后,大促下单高峰期几乎都快过了,此时才可能触发一次Full GC。因为按大促开始10min就有50万订单估算,其实大促开始后一堆用户等着下单,那1h就可能有两三百万订单,高峰期过后,基本订单系统访问压力就很小了,那GC问题也就不算啥了。所以经过新生代优化,可推算出基本上大促高峰期内,也就可能1h才1次Full GC,然后高峰期一过,随着订单系统慢慢运行,可能就要几h才有一次Full GC。

老年代GC时,会发生“Concurrent Mode Failure”吗?

假设订单系统运行1h后,老年代大概有900MB对象,剩余可用空间仅100MB,此时就会触发一次Full GC:

JVM老年代GC调优-鸿蒙开发者社区

CMS在并发清理阶段时,系统程序也可并发运行,所以此时老年代空闲空间仅100MB,然后系统程序还不断创建对象,万一这时系统运行触发某个条件,如有200MB对象要进入老年代,会咋样:

JVM老年代GC调优-鸿蒙开发者社区

这时就会触发“Concurrent Mode Failure”,因此时老年代无足够内存放200MB对象,就会导致立马进入STW,然后切换CMS为Serial Old,直接禁止程序运行,然后单线程进行老年代垃圾回收,回收掉900MB对象后,再让系统继续运行:

JVM老年代GC调优-鸿蒙开发者社区

这种事件概率挺小,因为必须是CMS触发Full GC时,系统运行期间还让200MB对象进入老年代。这种小概率的事件而言,有必要专门调整参数吗?暂时看没必要,无需针对小概率事件特意优化参数。此时JVM参数:“-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M - XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC - XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92”

CMS垃圾回收后,进行内存碎片整理的频率应该多高?

CMS完成Full GC后,一般需执行内存碎片整理,可设置多少次Full GC后,才执行一次内存碎片整理,真的有必要修改这些参数吗?没必要,因为大促高峰期,Full GC可能也就1h执行一次,大促高峰期后,就没那么多订单了,此时可能几h才有一次Full GC。所以就保持默认的设置,每次Full GC之后都执行一次内存碎片整理就可以,JVM参数:-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M - XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC - XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0

可见,Full GC优化前提是Minor GC优化,Minor GC优化前提是合理分配内存空间,合理分配内存空间前提是对系统运行期间的内存使用模型进行预估。很多Java系统,只要对系统运行期间的内存使用模型做好预估,然后分配好合理内存空间,尽量让Minor GC之后的存活对象留在Survivor里不要去老年代,然后其余的GC参数不做太多优化,系统性能基本不会太差。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
已于2022-1-24 22:49:50修改
1
收藏
回复
举报
回复
    相关推荐