前情提要
- 环境:k8s cgroup v1,
- JDK版本:OpenJDK_1.8.0_212-b04,该版本可感知容器内存CPU配置,默认开启UseContainerSupport,
- JVM监控:Prometheus+Grafana,
- 内存分配:POD limits 3G,
- 启动命令行JVM相关参数:
-XX:InitialRAMPercentage=80.000000 -XX:MaxRAMPercentage=80.000000 -XX:MetaspaceSize=268435456 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -Xloggc:/dev/shm/gc20240527172847.log
问题症状
每次Major GC后可用堆内存都会减少,继而导致频繁的Major GC和Stop The World,相关监控截图如下:
问题分析
对于上述问题通过“JVM 堆内存详细”监控可有基础的判断,但还需两个数据进行作证:
- 垃圾回收器:gc文件开头CommandLine flags中-XX:+UseParallelGC,即JDK8默认的垃圾回收器;
- 堆信息:jmap -heap pid
根据上面“堆信息”可以看到“SurvivorRatio=8”,但是实际Eden与From和To的比例并非8:1,然后分配的内存 2G多 才用了 4百多M,从“JVM 堆内存详细”监控中可看出每次Major GC后老年代的可用内存大小都有所下降,然后新生代(Eden+Survivor)的可用内存都小得可怜,这样程序运行时产生的对象无法存放在新生代就会直接丢到老年代,接着就是频繁的Major GC了。
再看使用的垃圾回收器:UseParallelGC,它默认开启了-XX:+UseAdaptiveSizePolicy,AdaptiveSizePolicy会在每次GC时根据GC情况对堆内存大小进行自动分配,以及对象从S区晋升到Old区的年龄阈值。
尝试解决
命令行启动参数增加 -XX:-UseAdaptiveSizePolicy 关闭AdaptiveSizePolicy。 参考文档:docs.oracle.com/javase/8/do…
持续观察
可以看到Eden与Survivor比例已经是8:1,大小也正常,未再出现频繁的Major GC和超长的GC暂停时间,基本该回收的对象都在新生代被回收。