JVM老年代GC调优 原创 精华
在新生代优化好的背景下,何时会让一些对象进入老年代?
-
-XX:MaxTenuringThreshold=5 让在1、2min内连续躲过5次Minor GC对象迅速进入老年代
一般就是@Service、@Controller之类的注解标注的那种系统业务逻辑组件,这种对象实例一般全局单实例,一直使用,所以一般会长期被GC Roots引用,这种对象一般不会太多,大概最多一个系统就几十MB
-
按JVM参数,若分配一个超过1MB大对象,如创建大数组或大List,直接进入老年代
这种大对象我们假设在这个案例里无,所以忽略不计。
-
Minor GC后,可能存活的对象超过200MB,Survivor放不下或是一下子占到超过Survivor的50%,此时会有一些对象进入老年代
但之前对新生代的JVM参数进行优化,就是为避免这种情况,所以这种概率很低。虽说很低,也不是完全没可能,比如某次GC后,可能刚好机缘巧合有超过200MB的对象,就会进入老年代。
假设就是订单系统在大促期,每隔5min会在Minor GC后有一小批对象进入老年代, 大概200MB:
大促期多久触发一次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:
CMS在并发清理阶段时,系统程序也可并发运行,所以此时老年代空闲空间仅100MB,然后系统程序还不断创建对象,万一这时系统运行触发某个条件,如有200MB对象要进入老年代,会咋样:
这时就会触发“Concurrent Mode Failure”,因此时老年代无足够内存放200MB对象,就会导致立马进入STW,然后切换CMS为Serial Old,直接禁止程序运行,然后单线程进行老年代垃圾回收,回收掉900MB对象后,再让系统继续运行:
这种事件概率挺小,因为必须是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参数不做太多优化,系统性能基本不会太差。