JVM 容器化内存溢出 (OOM) 治理
在 Docker/Rancher 环境中,Java 应用的内存管理与传统虚拟机有巨大差异。为了防止容器被操作系统(OS OOM Killer)无情击杀,并确保 OOM 发生时能留下“临终遗言”(Dump 和告警),请遵循以下配置。
1. 内存分配核心逻辑
为什么不能设为 100%?
很多开发者错误地将容器 Limit 设为 4GB,然后把 JVM -Xmx 也设为 4GB。
Java 进程总占用 = Heap + Metaspace + CodeCache + DirectBuffer (NIO) + Thread Stack + JVM Overhead。
如果堆占满了容器限制,哪怕只有一点点堆外内存请求,容器也会因超过 Cgroups 限制被 OS 直接 kill -9,此时连 Dump 都来不及生成。
经验设置公式:
- -Xmx / MaxRAMPercentage = 容器内存 Limit 的 70% ~ 75% 。
- 预留 25% ~ 30% = 给元空间、线程栈(每个线程默认 1MB)、Netty 直接内存及 OS 开销。
2. JVM 启动参数(推荐方案)
方案 A:现代优雅写法(推荐)
适用于 JDK 8u191+ / 11 / 17 / 21。能自动感知容器限制,无需手动计算内存数值。
Bash
java \
-XX:InitialRAMPercentage=75.0 \
-XX:MaxRAMPercentage=75.0 \
-XX:MaxMetaspaceSize=256m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/app/logs/dump/ \
-XX:OnOutOfMemoryError="/app/scripts/oom_alarm.sh" \
-XX:+ExitOnOutOfMemoryError \
-jar app.jar
注意:
-XX:+ExitOnOutOfMemoryError必须放在-XX:OnOutOfMemoryError之后。JVM 会先尝试执行脚本,脚本执行完成后立即强制退出,确保 Rancher 能够感知到容器失败并触发重启。
3. 钉钉告警与现场保留脚本
脚本路径: /app/scripts/oom_alarm.sh
功能: 在 JVM 崩溃前,抓取瞬间的系统负载、内存状态,并向钉钉发送详细 Markdown 告警。
Bash
#!/bin/bash
# ==========================================
# 1. 核心配置
# ==========================================
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=你的_TOKEN"
APP_NAME="SpringBoot-Production"
LOG_DIR="/app/logs/oom_status"
DATE_STR=$(date '+%Y%m%d_%H%M%S')
SYS_LOG_FILE="${LOG_DIR}/oom_info_${DATE_STR}.log"
mkdir -p "$LOG_DIR"
# 获取环境信息
CONTAINER_IP=$(hostname -i || echo "Unknown IP")
CONTAINER_ID=$(hostname)
CURRENT_TIME=$(date '+%Y-%m-%d %H:%M:%S')
# ==========================================
# 2. 收集快照 (防止进程退出太快导致命令失效)
# ==========================================
{
echo "========== OOM 发生时间: $CURRENT_TIME =========="
echo -e "\n[1. 内存状态]\n$(free -m || echo "free cmd fail")"
echo -e "\n[2. 进程状态]\n$(top -b -n 1 | head -n 20 || echo "top cmd fail")"
echo -e "\n[3. 磁盘空间]\n$(df -h || echo "df cmd fail")"
} > "$SYS_LOG_FILE" 2>&1
# ==========================================
# 3. 发送钉钉 Markdown 告警
# ==========================================
# 使用转义确保 JSON 安全
SAFE_APP_NAME=$(echo "$APP_NAME" | sed 's/"/\"/g')
JSON_PAYLOAD=$(cat <<EOF
{
"msgtype": "markdown",
"markdown": {
"title": "OOM告警",
"text": "### [OOM告警] 容器即将崩溃!\n\n---\n- **应用名称**: $SAFE_APP_NAME\n- **容器 IP**: $CONTAINER_IP\n- **容器 ID**: $CONTAINER_ID\n- **发生时间**: $CURRENT_TIME\n\n**说明**: JVM 发生 `OutOfMemoryError`。Heap Dump 已生成,容器即将退出并重启。\n\n**现场快照**: \n> `$SYS_LOG_FILE`\n\n请运维人员及时到宿主机挂载目录提取 .hprof 文件进行分析。"
},
"at": { "isAtAll": true }
}
EOF
)
curl -s -X POST "$DINGTALK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD" >> "${LOG_DIR}/dingtalk_curl.log" 2>&1
4. 挂载与持久化建议
由于容器重启后内部文件会丢失,必须将 Dump 目录挂载到宿主机(Rancher 卷挂载):
| 容器内路径 | 宿主机路径(示例) | 作用 |
|---|---|---|
/app/logs/dump/ | /data/rancher_dumps/app_name/ | 存储几百MB甚至数GB的 .hprof 文件 |
/app/logs/oom_status/ | /data/rancher_logs/app_name/ | 存储 OOM 发生时的系统负载日志 |
5. 常见问题检查清单 (Checklist)
- 权限确认: 在 Dockerfile 中确保脚本可执行:
RUN chmod +x /app/scripts/oom_alarm.sh。 - 工具检查: 基础镜像(如 Alpine)可能没有
curl。如果脚本无效,请在 Dockerfile 中安装:apk add --no-cache curl或apt-get install -y curl。 - Rancher 设置: Memory Limit 必须是硬限制(Hard Limit),不能只设 Reservation(预留)。
- Dump 清理:
.hprof文件很大,建议在宿主机配置定时任务(Crontab),清理 7 天以前的 Dump 文件。