今天公司项目收到线上告警,某个集群监控到OOM了,于是立马去服务器上查看相关日志:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /applog/java_pid69.hprof ...
Unable to create /applog/java_pid69.hprof: File exists
> 看到这边顿时心里凉了半截, 该打的dump文件没生成, 到 /applog目录下看对应的 dump生成时间, 果然是几天之前的
查看 JVM 启动参数:
~~~properties
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/applog
是因为这个配置,只会在OOM时候生成 java_pid.hprof的文件么, 没有自动添加时间戳?
于是, 我自己本地搭建了一个项目,验证这个问题:
package com.itboat008.springboot;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class SpringBootDemoApplicationTests {
@Test
void contextLoads() {
List<Object> list = new ArrayList<>();
while(true){
list.add(new byte[1024 * 1024]);
}
}
}
VM options:
-Xms2048M -Xmx2048M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/applog
运行,果然只有pid, 没有自动追加时间戳
于是想到将自己的spring-boot项目部署到阿里云上, 在docker里面配置验证OOM打dump追加时间戳。
其他的源码可参考 之前的文章:
spring-boot整合thymeleaf和MyBatis
在此基础之上, 添加 一个get请求, 可访问这个请求,触发OOM打dump
UserController 添加 如下代码:
@GetMapping("/testOOM")
@ResponseBody
public ResultVO testOOM(){
ResultVO resultVO = new ResultVO();
List<Object> list = new ArrayList<>();
for(int i = 0 ; i < 10000; i++){
list.add(new byte[1024 * 1024]);
}
resultVO.setResFlag("1");
resultVO.setResInfo("asd");
return resultVO ;
}
打包, 生成 jar包,上传至阿里云服务器 /home/docker/oom-test目录, 并修改 该jar包的执行权限
重命名jar名称为 app.jar
写Dockerfile
- 进入主机的 /home/docker/oom-test目录, vim Dockerfile
Dockerfile:
FROM java:8
MAINTAINER itboat008
#添加主机路径下的 app.jar到 镜像的 /home/docker/oom-test/路径
ADD ./app.jar /home/docker/oom-test/app.jar
ADD ./heapDump.sh /home/docker/oom-test/heapDump.sh
#设置 OnOutOfMemoryError 时候需要执行的脚本
ENV JAVA_OPTS="-Xms256M -Xmx256M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/applog -XX:OnOutOfMemoryError='/home/docker/oom-test/heapDump.sh'"
VOLUME ["/applog"]
ENTRYPOINT java ${JAVA_OPTS} -jar /home/docker/oom-test/app.jar
其中 heapDump.sh如下:
#在该脚本下修改hprof的文件名
datetime = $(date "%Y%m%d%H%M%S")
hprofs = `find /applog -name '*.hprof'`
for tmprof in $hprofs
do
mv $tmprof `echo "$tmprof.$datetime"`
done
主机上/home/docker/oom-test路径下的文件如下:
制作镜像
cd /home/docker/oom-test
docker build -t oom-test:foxer .
# 查看当前所欲镜像
docker images
#启动容器
docker run -d -p 8888:8080 --name oom-test2009 oom-test:foxer
#查看容器
docker ps
#跟踪容器日志
docker logs -f d63fbe05f043
触发OOM验证
打开浏览器 , 访问 对应的url, 可发现浏览器返回了500的错
跟踪容器日志:
进入容器 /applog路径下查看:
docker exec -it d63fbe05f043 bash
发现已经生成了, 再看下主机路径下对应文件
#查看当前容器信息
docker inspect d63fbe05f043
找到 Mounts这块的配置
#进入到宿主机对应路径
cd /var/lib/docker/volumes/1b6f6094ee975ee3662316d8777aca5e5a8e07f26243699fd5fcaf70a3aaf7d3/_data
发现确实持久化到了主机的/var/lib/docker/volumes/1b6f6094ee975ee3662316d8777aca5e5a8e07f26243699fd5fcaf70a3aaf7d3/_data 路径上
指定主机生成路径
#如果需要指定生成的路径:
docker run -d -p 8888:8080 -v /applog:/applog --name oom-test2116 oom-test:foxer
#查看已启动的容器信息
docker inspect `docker ps | grep 'oom-test:foxer' |awk '{print $1}'`
发现确实挂载到主机的/applog路径下了
访问对应请求,发现生成到了/applog
经验证,如果当前应用已经打过dump了, 那么只要应用不重启, 后续OOM就不会再自动打dump了,只会打印异常堆栈