JDK8在容器中的问题记录-20240527

539 阅读2分钟

前情提要

  • 环境: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,相关监控截图如下:

image.png

image.png

image.png

问题分析

对于上述问题通过“JVM 堆内存详细”监控可有基础的判断,但还需两个数据进行作证:

  • 垃圾回收器:gc文件开头CommandLine flags中-XX:+UseParallelGC,即JDK8默认的垃圾回收器;
  • 堆信息:jmap -heap pid

image.png

根据上面“堆信息”可以看到“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…

持续观察

image.png

image.png

可以看到Eden与Survivor比例已经是8:1,大小也正常,未再出现频繁的Major GC和超长的GC暂停时间,基本该回收的对象都在新生代被回收。