JVM 内存溢出(OOM)通常表现为应用抛出 java.lang.OutOfMemoryError 异常,或者进程/容器在没有明显日志的情况下突然崩溃退出。排查的核心思路是“获取现场(Dump文件)” -> “分析现场” -> “对症下药”。
以下是针对 CentOS 7 宿主机和 Docker 容器环境的具体排查与解决全流程方案:
🚨 第一步:快速确认 JVM OOM 现象
- 查看应用日志:直接在控制台或日志文件中搜索
java.lang.OutOfMemoryError。常见的类型有:Java heap space(堆内存溢出,最常见)Metaspace(元空间溢出,加载了太多类)Direct buffer memory(堆外直接内存溢出)
- 检查进程/容器状态:
- CentOS 7:如果 Java 进程突然消失,使用
sudo dmesg -T | grep -i "oom\|killed process"查看是否被系统 OOM Killer 强制杀掉。 - Docker:如果容器反复重启,执行
docker inspect <容器ID> | grep -A 5 '"State"'。若"OOMKilled": true且"ExitCode": 137,说明容器因内存超限被杀。
- CentOS 7:如果 Java 进程突然消失,使用
💻 第二步:CentOS 7 宿主机环境的排查与解决
1. 排查:获取并分析“案发现场”
当 OOM 发生时,我们需要拿到那一刻的堆内存快照(Heap Dump)来分析是谁占用了内存。
- 提前配置(最佳实践):在 Java 启动命令中加入以下参数,让 JVM 在崩溃时自动生成 Dump 文件:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heap_dump.hprof - 手动抓取(应急方案):如果进程还在但响应极慢,可以先用
jps或top找到 Java 进程 PID,然后手动导出堆快照:jmap -dump:format=b,file=/tmp/heap_dump.hprof <PID> - 分析 Dump 文件:将生成的
.hprof文件下载到本地,使用 Eclipse MAT (Memory Analyzer Tool) 或 VisualVM 打开。重点查看 "Leak Suspects"(泄漏疑点)报告和 "Dominator Tree"(支配树),找出占用内存最大的对象及其引用链。
2. 解决与调优
- 调整 JVM 内存参数:如果分析后发现没有内存泄漏,只是业务量增大导致内存不够,可以适当调大堆内存:
-Xms2g -Xmx2g # 将初始和最大堆内存设置为相同值,避免运行时频繁扩容 - 排查代码内存泄漏:如果 MAT 分析发现某个
List、Map或缓存对象异常庞大,说明代码中存在只存不删的逻辑,需要优化代码(如改用有界队列、设置缓存过期时间)。 - 排查线程与元空间:如果是
Metaspace溢出,可增加-XX:MaxMetaspaceSize=512m;如果是线程过多导致无法创建新线程,需检查代码中是否有未关闭的线程池。
🐳 第三步:Docker 容器环境的排查与解决
Docker 环境下的 JVM OOM 往往更复杂,因为涉及“JVM 视角”和“Docker 视角”两层内存限制。
1. 排查:定位内存瓶颈
- 实时监控:使用
docker stats查看容器的内存使用量(MEM USAGE)是否已经触顶设定的限制(MEM LIMIT)。 - 获取 Dump 文件:
在容器内执行jmap命令(如果镜像内没有 JDK 只有 JRE,可能需要临时安装或使用docker cp将工具拷入):docker exec -it <容器ID> jmap -dump:format=b,file=/tmp/dump.hprof <PID> docker cp <容器ID>:/tmp/dump.hprof ./ # 将文件拷出到宿主机分析
2. 解决与调优(核心:让 JVM 感知容器限制)
Docker 环境下 OOM 的最大误区是:JVM 以为宿主机有 64G 内存,于是申请了 16G 堆内存,但 Docker 只给容器分配了 4G,导致容器直接被 Docker 引擎杀掉。
- 启用容器内存感知(关键配置):
确保使用 JDK 8u191 及以上版本,并在启动参数中加入-XX:+UseContainerSupport。这会让 JVM 自动识别 Docker 的 cgroup 内存限制。 - 使用百分比动态设置堆内存:
不要硬编码-Xmx,改用百分比参数,让 JVM 根据容器限制自动计算。通常建议将堆内存设为容器限制的 75% 左右,预留空间给线程栈、元空间和堆外内存:-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=75.0 - 严格限制容器资源:
在启动 Docker 容器或编写docker-compose.yml时,务必设置内存上限,防止单个容器耗尽宿主机资源:docker run -d --memory="2g" --memory-swap="2g" my-java-app
总结建议:
遇到 JVM OOM,先拿 Dump 文件分析是“真泄漏”还是“假不够”。在 Docker 环境下,“限制容器内存 + 开启 -XX:+UseContainerSupport + 配置 MaxRAMPercentage” 是防止 JVM 盲目申请内存导致被杀的黄金法则。
