要使-XX:OnOutOfMemoryError真正执行脚本,必须满足三个条件:脚本具有可执行权限、使用绝对路径、Java进程对脚本及依赖目录有读+执行权限;仅支持%p和%t占位符,脚本需以#!/bin/bash开头,避免依赖shell解析或管道重定向。

怎么让 -XX:OnOutOfMemoryError 真正执行你的脚本
它默认不执行,除非你同时满足三个硬性条件:脚本有可执行权限、路径是绝对路径、Java 进程对脚本和其依赖目录有读+执行权限。
常见失败现象:OnOutOfMemoryError 参数被静默忽略,日志里完全没输出;或报错 Cannot run program "/opt/scripts/oom.sh": error=13, Permission denied。
- 脚本必须用
chmod +x /opt/scripts/oom_alert.sh加上执行权限 - 不要写成
-XX:OnOutOfMemoryError="./oom.sh"或"sh oom.sh"—— JVM 不走 shell 解析,只认绝对路径的可执行文件 - 脚本内避免依赖当前工作目录(
pwd不可靠),所有路径都用绝对路径,比如日志写入/var/log/oom_alert.log而不是./alert.log - 如果脚本要发 HTTP 告警,别用
curl但没装;建议提前验证:sudo -u $JAVA_USER /opt/scripts/oom_alert.sh 12345
如何把进程 PID 和时间戳传给告警脚本
-XX:OnOutOfMemoryError 支持有限的占位符,只有 %p(PID)和 %t(Unix 时间戳)可用,没有 %h 或 %d。它们会被 JVM 自动替换,不需要脚本自己解析。
示例参数写法:-XX:OnOutOfMemoryError="/opt/scripts/oom_alert.sh %p %t",对应脚本内可通过 $1 拿到 PID,$2 拿到时间戳。
- 脚本第一行务必写
#!/bin/bash,否则%p替换后可能因解释器缺失而失败 - 别在参数里拼接字符串,比如
-XX:OnOutOfMemoryError="echo OOM at %t | logger"—— 管道和重定向不被支持,只会当作一个命令名去执行 - 如需记录更详细上下文(比如主机名、服务名),建议在脚本里用
hostname、basename $(readlink -f /proc/$1/exe)等命令动态获取
自动重启不能只靠 -XX:OnOutOfMemoryError
这个参数只负责“触发”,不负责“等待”或“判断是否成功”。它调用脚本后立即返回,JVM 继续走退出流程(除非你加了 -XX:+ExitOnOutOfMemoryError 但没配好)。
想实现真正可控的重启,必须分两层:脚本里做守护动作,外部再配进程管理器兜底。
- 脚本内不要直接
exec java ...启动新实例 —— 容易和原进程 PID 混淆,且无法保证旧进程已死干净 - 推荐做法:脚本只发告警 + 写标记文件(如
/tmp/restart_pending_$(hostname)),由 systemd 或 supervisor 监控该文件并触发 restart - 如果你硬要在脚本里拉起新进程,务必用
nohup java ... & disown并检查$!是否非零,否则重启失败也不会暴露 - 注意:K8s 场景下,不要在容器内搞自重启 —— 应依赖
livenessProbe触发重建 Pod,否则可能掩盖真正的 OOM 根因
为什么有时脚本执行了但告警没收到
最常被忽略的是资源隔离问题:JVM 进程运行在容器或受限用户下,脚本能跑通,但网络或系统调用被拦截。
典型表现:脚本里的 curl 超时、logger 不写入 syslog、邮件命令静默失败。
- 先确认脚本在相同用户+相同 namespace 下手动执行是否正常:
sudo -u appuser nsenter -t $(pgrep -f 'java.*YourApp') -U -n -p -- /opt/scripts/oom_alert.sh 12345 - 容器中若用了
seccomp或capabilities限制,curl可能缺NET_BIND_SERVICE,改用wget --no-check-certificate更稳妥 - 别依赖脚本里的
sleep 2等待 —— JVM 在调用完脚本后可能几毫秒内就退出,脚本后台任务容易被 SIGTERM 中断 - 务必在脚本开头加
set -euo pipefail并重定向所有输出到日志,否则 silent fail 很难排查

















