一、问题
线上服务器部署后,服务器普遍内存较高,应用内存被打满,导致服务器不定时自动重启
二、解决过程
(1)通过free -h,查看内存使用占比
(2)通过top命令实时查看内存使用情况,发现是java程序占比最高
(3)查看java进程内存,解析下来发现Metaspace 元数据空间使用率过高
# 查看Java进程ID
jps -l
# 查看Java进程(PID=7)的GC和内存使用情况
jstat -gcutil <PID>
(4)调整docker启动命令的参数配比,增大元数据空间
(5)没有作用,然后怀疑应用堆内存过大
# 查看Java堆内存使用情况
jmap -heap <PID>
(6)下载dump日志,查看具体内容,在dockerfile文件启动命令中加上打印日志的命令
-XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/heapdump.hprof
(7)解析dump文件后,没发现堆内存有啥问题,由于在(5)中发现最大堆内存远远大于我们设置的占比的值,然后决定将配置参数将百分比形式调整成固定大小的形式
ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMPercentage=60.0 -XX:InitialRAMPercentage=40.0 -XX:MinRAMPercentage=40.0 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/heapdump.hprof"
ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xms800m -Xmx1200m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/heapdump.hprof"
这些 JVM 参数用于在容器环境中优化 Java 应用的运行。下面是各参数的作用:
参数详解
1. -XX:+UnlockExperimentalVMOptions
作用:启用实验性 JVM 选项
说明:某些参数(如 UseCGroupMemoryLimitForHeap)需要先开启此选项
2. -XX:+UseCGroupMemoryLimitForHeap
作用:让 JVM 根据容器的 cgroup 内存限制来设置堆大小
说明:在容器中,JVM 默认可能使用宿主机内存,开启后按容器限制计算堆大小
注意:这是较旧的参数,Java 11+ 已内置容器感知,通常不再需要
3. -XX:MaxRAMPercentage=60.0
作用:最大堆内存 = 可用内存 × 60%
说明:基于容器可用内存的百分比设置最大堆,适合容器环境
4. -XX:InitialRAMPercentage=40.0
作用:初始堆内存 = 可用内存 × 40%
说明:JVM 启动时的初始堆大小
5. -XX:MinRAMPercentage=40.0
作用:最小堆内存 = 可用内存 × 40%
说明:堆内存的下限,防止堆过小
6. -XX:MetaspaceSize=128m
作用:元空间初始大小为 128MB
说明:元空间用于存储类元数据(替代永久代)
7. -XX:MaxMetaspaceSize=256m
作用:元空间最大大小为 256MB
说明:限制元空间上限,防止无限增长
8. -XX:+UseG1GC
作用:启用 G1 垃圾收集器
说明:适合大堆和低延迟场景
9. -XX:MaxGCPauseMillis=200
作用:目标最大 GC 暂停时间为 200 毫秒
说明:G1 会尽量控制单次 GC 暂停时间在 200ms 以内(目标值,不保证)
10. -XX:+HeapDumpOnOutOfMemoryError
作用:发生 OOM 时自动生成堆转储文件
说明:便于事后分析内存问题
11. -XX:HeapDumpPath=/app/heapdump.hprof
作用:指定堆转储文件的保存路径
说明:OOM 时会在 /app/heapdump.hprof 生成转储文件
(8)发现内存降了下来,然后就思考为啥百分比形式不生效,而且最大堆内存值不合理
(9)百度后,有个结论是cgroup版本有问题,然后查看服务器cgroup版本,使用命令查看最大堆内存的配置,比较dev和线上环境的配置
# 查看 Java 进程的堆内存配置信息
jmap -heap 7 | grep -E "MaxHeapSize | Heap Configuration"
# 查看系统中与 cgroup(控制组)相关的挂载点
mount | grep cgroup
测试
线上
(10)最后发现线上使用的cgroup2,和jdk版本不兼容,导致配置百分比不生效
三、解决方法
将jdk版本从openjdk version "1.8.0_212"升到openjdk version "1.8.0_402"