背景
因为同事的离职,半路被迫接手的一个可视化项目,使用ElasticSearch作为OLAP数据库、Docker作为部署工具等,突然有一天项目现场环境出现JVM内存溢出问题,被迫披挂上阵定位问题的原因
分析过程
top
命令查看系统资源占有情况,cpu占用不高,内存占用高,并且虚拟内存高达16g
Tips:Java 程序由于自己维护堆的使用,导致调用 glibc 去管理内存的次数较少。更糟的是 Java 8 开始使用 metaspace 原空间取代永久代,而元空间是存放在操作系统本地内存中,那线程一多,每个线程都要使用一点元空间,每个线程都分配一个arena,每个都64MB,就会导致巨大的虚拟地址被分配。
free -h
查看内存占有,buffer/cache占有很大,总内存剩余7g
Tips:buffers是用来缓冲块设备做的,它只记录文件系统的元数据(metadata)以及 tracking in-flight pages,而cached是用来给文件做缓冲。更通俗一点说:buffers主要用来存放目录里面有什么内容,文件的属性以及权限等等。而cached直接用来记忆我们打开过的文件和程序。
通过系统日志中的java.lang.OutOfMemoryError
与系统资源占用情况基本定位是jvm内存溢出造成的
进一步使用jdk自带性能监控工具查找原因
-
jmap -heap 1 //查看堆内存情况,
无此命令参数
,因为该服务使用的是docker镜像的openjdk导致缺失部分jdk完整工具支持 -
jstat -gc 1 250 10 //查看垃圾回收gc状态情况,新生代与老年代内存基本耗尽,FullGC高达
1200
多次,但是未释放成功 -
jmap -histo:live 1 | head -n 100 //查看top前100的实例数量情况,再次受阻,报错
Unable to get pid
执行jmap
命令遇到的Unable to get pid
可以采用的解决方案:
- 在docker run时加上
--init
参数 - 安装
Tini
,使用tini作为入口进程,配置启动java进程
接下来,我们使用Alibaba开源的诊断工具Arthas可视化工具排查问题(PS:如果大家对Arthas不了解,可以查看官方文档)
下载arthas全量jar>拷贝到容器内>成功启动jar
docker cp 解压绝对路径 platform:/tmp/
docker exec -it platform /bin/sh -c "cd /tmp/arthas; java -jar arthas-boot.jar"
执行dashboard
查看资源占有情况
通过dashboard
了解到资源已基本耗尽,线程执行基本正常,最终需要通过导出dump文件分析内存分布进行分析
执行heapdump /tmp/dump.hprof
导出dump文件,下载dump文件到本地,通过MAT工具进行分析
最终定位问题是因为项目中未正确使用缓存导致的
参考
mac下安装MAT进行分析
openjdk-alpine容器中的jvm如何执行dump