诉求项目存在JVM内存溢出,排查发现JVM默认占用Pod资源的1/4,给定4g,占用了1g;

如果容器资源没有设置任何 limits 并且Java没有设置额外参数的话,Java应用会默认使用宿主机 1/4 的内存作为 MaxHeapSize

1、JVM内存占用分析

JAVA项目容器化部署JVM配置 - 图1

  • 未分配JVM内存时的使用情况(2G)

JAVA项目容器化部署JVM配置 - 图2

1. 检查JDK是否支持UseContainerSupport

首先查看使用JDK版本是否支持 UseContainerSupport 参数,如果支持该参数,则JVM会自动读取容器限制的内存值,读取文件:/sys/fs/cgroup/memory/memory.limit_in_bytes

该参数在 Java 8u191+,10以及更高的版本中支持

  • 检查方法(bool值,返回true表示支持,反之不支持):
  1. $ java -XX:+PrintFlagsFinal -version | grep UseContainerSupport
  2. bool UseContainerSupport = true {product}
  3. openjdk version "1.8.0_342"
  4. OpenJDK Runtime Environment (build 1.8.0_342-b07)
  5. OpenJDK 64-Bit Server VM (build 25.342-b07, mixed mode)

2. 支持UseContainerSupport

-XX:MaxRAMPercentage 参数来配置JVM可用容器限制的资源百分比,默认是25.0 ;取值范围是 0.0 到 100.0

  • 检查当前堆内存的大小:(2147483648Bytes=2097152KB=2048MB=2GB)
  1. $ java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
  2. uintx MaxHeapSize := 2147483648 {product}
  3. openjdk version "1.8.0_342"
  4. OpenJDK Runtime Environment (build 1.8.0_342-b07)
  5. OpenJDK 64-Bit Server VM (build 25.342-b07, mixed mode)
  • 修改JVM可用Pod容器资源限制的百分比80:(6878658560Bytes=6717440KB=6560MB=6.40625GB)
  1. java -XX:MaxRAMPercentage=80.0 -XX:+PrintFlagsFinal -version | grep MaxHeapSize
  2. uintx MaxHeapSize := 6878658560 {product}
  3. openjdk version "1.8.0_342"
  4. OpenJDK Runtime Environment (build 1.8.0_342-b07)
  5. OpenJDK 64-Bit Server VM (build 25.342-b07, mixed mode)

1)修改Dockerfile并在部署时添加环境变量来验证

由于之前的Dockerfile中的变量没有设置该参数所以需要设置环境变量来替代,以后直接在Dockerfile中设置合适不用在动态配置环境变量:建议值:<font style="color:#E8323C;">-XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0</font>或者<font style="color:#E8323C;">-XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0</font>

不能是整数:70、75(可能是jdk的一个bug)

JAVA项目容器化部署JVM配置 - 图3

JAVA项目容器化部署JVM配置 - 图4

2)建议配置参数

  1. -XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof
  2. # 配置到tmp目录中
  3. -XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/tmp/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump-${POD_IP}-$(date '+%s').hprof
  4. -XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/tmp/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump-${POD_IP}-$(date '+%s').hprof

🎯**说明** 应用程序出现OOM问题时,会触发Linux内核的OOM Killer机制。该机制能够监控占用过大内存,尤其是瞬间消耗大量内存的进程,然后它会强制关闭某项进程以腾出内存留给系统,避免系统立刻崩溃。

3. 不支持UseContainerSupport

  • -Xms2g -Xmx2g 配置JVM内存
  • -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 配置占比

1)-Xms1000m -Xmx2000m方式

⚠️弊端:每次修改Pod容器限制后都需要手动修改相关环境变量来更改配置

  • 推荐参数
  1. -Xms2048m -Xmx2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof
其中Dump文件路径/home/admin/nas为NAS挂载目录:
  1. 当应用发生OOM时,会生成堆转储文件到NAS挂载目录,您可以利用ossutil工具,将该Dump文件下载到本地进行分析。具体操作,请参见通过日志上传下载诊断应用
  2. -Xmx 参数即使设置了2000m,JVM也会分配比2000m多的内存,如果想将JVM完全限制在2000m以内,需要使用 -XX:MaxRAM 参数

2)-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap方式

⚠️弊端:对于 Java SE 8u121 以及之前的版本这些参数都不生效,只能通过<font style="color:rgb(51, 51, 51);"> -Xms1000m -Xmx2000m </font>这种方式进行限制

使用 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 参数来让JVM读取容器限制 不过默认即使加上这两个参数,JVM也只会使用容器限制内存的 1/4 的量,可以再添加一个参数 -XX:MaxRAMFraction ,该参数表示使用可用内存的基数,默认是4 JVM可用最大heap内存=最大可用内存*1/MaxRAMFraction
  1. java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XX:+PrintFlagsFinal -version | grep MaxHeapSize
如果配置了 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap 参数,则JVM使用容器限制内存的 1/4,即4000m*1/4=1000m 如果配置了 -XX:MaxRAMFraction=2 ,则JVM使用容器限制内存的 1/2,即 4000m*1/2=2000m

:::color2 🎯设置 MaxRAMFraction 为1表示将容器所有内存分配给JVM,不建议这么做,需要给其他程序预留部分内存

Java 10 移除了 -XX:{Initial|Min|Max}RAMFraction 参数

:::

:::color1 参考链接:

https://help.aliyun.com/document_detail/383255.html

https://blog.csdn.net/qq_34556414/article/details/121101809

https://www.cnblogs.com/xiaoqi/p/container-jvm.html

:::