当Java程序发生OOM(OutOfMemoryError)时,如果想要自动转储堆内存以便分析,可以在启动JVM时配置下列参数:
-XX:+HeapDumpOnOutOfMemoryError
这个参数可以让JVM在抛出OOM异常时自动生成heap dump文件。
-XX:HeapDumpPath=./java_pid<pid>.hprof
指定生成的heap dump文件的存放路径和文件名,这里使用了pid作为文件名的一部分,可以将不同时间的堆转储区分开。
-XX:OnOutOfMemoryError=<命令>
当OOM发生时,可以执行指定的命令,例如用于通知或执行脚本。
所以典型的带有堆转储的启动参数可以是:
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_pid<pid>.hprof -Xmx512m ...
这样就可以在OOM发生时自动获取堆转储文件。分析这个文件可以定位内存泄漏或其他内存问题。
hprof文件自定义
一开始我想要使用生成时间作为heap dump文件名的一部分,配置方法如下:
-XX:HeapDumpPath=./java_oom-%t.hprof
经过实验,果然不行,原因是 HeapDumpPath 中如果使用 %t 或者 %%t 作为时间戳占位符,在运行时实际上并不能被 JVM 解析替换。
经过询问claude,得到两个解决方案,但是我还没实验,等验证过再来更新,先记录上
1.使用时间戳+计数器的方式生成唯一堆转储文件名
主要思路是:
- 启动Java程序时,使用一个循环脚本wrap着它
- 这个循环脚本中会不停地监听Java进程的PID
- 当检测到OOM产生时,脚本中会做两件事:
3.1 动态设置HeapDumpPath参数,即堆转储文件的路径,文件名使用时间戳+计数器确保唯一性
3.2 向Java进程发送kill -3 {pid} 信号,触发堆转储
这里的关键就是一旦监听到OOM事件,动态设置一个唯一的文件名,然后通知Java进程进行堆转储。
具体示例脚本代码:
# oom_wrap.sh COUNT=1 while true; do PID=$(pgrep -f java) if [ $? -ne 0 ]; then echo "Java process died" exit 1 fi FILE=heapdump-${DATE}+${COUNT}.hprof gsignal -s 3 -p heapdumpfile $FILE $PID let COUNT=COUNT+1 sleep 10 done
启动Java程序时,用这个脚本包着:
./oom_wrap.sh java -Xmx128m ...
这里提到的gsignal是用来向进程发送信号的工具。
每次转储后复制文件到新的唯一文件名中
可以使用 Linux 的 crontab 定时任务来完成。
例如配置一个每小时执行的脚本:/path/to/heapdump-copy.sh
# heapdump-copy.sh DUMP_DIR=/path/to/dumps cd $DUMP_DIR if [ -n "$(ls -1t java_oom_*.hprof | head -n 1)" ]; then NEWEST_DUMP=$(ls -1t java_oom_*.hprof | head -n 1) NEW_FILENAME=java_oom_$(date +%Y%m%d%H%M).hprof cp $NEWEST_DUMP $NEW_FILENAME echo "Copied new heap dump file $NEW_FILENAME" fi
脚本逻辑:
- 检查 dumps 目录下最新的 java 堆转储文件
- 如果存在的话,复制到带时间戳的新文件名中
然后在 crontab 中增加定时任务:
0 * * * * /path/to/heapdump-copy.sh
这样就可以每小时检查并复制最新生成的 OOM 堆转储文件了。
crontab 定时任务还可以结合日志记录、监控报警使用,让自动化堆转储复制更加可靠。