总结下工作中遇到的GC问题。
一、JVM参数设置不当
机器4G,部分JVM参数设置
-Xmx3296m -Xms3296m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxGCPauseMillis=200 -XX:+UseG1GC
-XX:-OmitStackTraceInFastThrow -XX:MinHeapFreeRatio=30 -XX:MaxHeapFreeRatio=50
现象:young GC比较频繁,并且不稳定,每1、2个小时会出现YGC次数激增,偶尔伴随Full GC,此时CPU状态也上升到100%。
查看日志:
分析原因:G1 GC动态调整新生代大小(默认5%-60%),从日志看出调整后的Eden区太小,导致很容易发生young gc。
解决方案:
调整JVM参数,将新生代下限从5%调整到35%:
-XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=35 -XX:G1MaxNewSizePercent=60
补充:Google时,看到说因为G1需要Mixed GC,Mixed GC会扫描Eden,Survivor,和1/8(默认)Old。因为比Young GC多扫描Old区,所以为了尽力实现最大停顿时间,在Mixed GC前的Young GC急剧缩小了Eden Heap的大小,所以调大最大停顿时间也能缓解此问题。
结果:YGC次数更平稳,FGC未出现
二、代码不合理
现象:每3小时出现一次YGC时间超过超过300ms,此时CPU和网络状态正常,只有内存增加。
当时排障的日志没截图保存下来,简单说一下主要耗时就是Object Copy占了大量时间,如图红色框部分
日志中各参数含义可以参考:
分析:通过代码发现每3个小时会全量拉一批某表的信息做本地缓存。Object Copy占了大量时间,说明需要Copy的对象很多。因为是缓存对象,所以不能回收。
解决方案:使用增量刷新的方式。伪代码:
int total = getTotalCount();
int pageSize = 500;
int(int i = 0 , i < total, i += pageSize){
List list = exec("select * from my_table limit ?,?", i ,pageSize)
CACHE.putAll(list);
}
采用这种方法需要注意的点:
1.可能导致刷新的数据不全,在刷新缓存时如果有新数据插入,则可能导致部分的缓存没有刷新。该问题的优化方式之一是:更新完缓存后看缓存的size是否等于表中数据的条数,如果不等于,说明刷新缓存过程中出现了变动,可以重新更新缓存(CAS思想)。
2.这种改进方法降低了吞吐量,一般来说这种定时任务都是以吞吐量为优先,改进这个问题是因为该问题影响了我系统的其他实时性功能,所以需不需要改进还是根据自己系统的需求。