绪言
时间会使人阅历丰富,但不是所有人都能丰富阅历,只有那些经常记录总结思考沉淀的人才会丰富。好记性不如烂笔头,况且,花了一定的功夫才研究出来的方案时间一长总是会忘的,下次再遇到还要花这些时间就很不明智了。所以催生了自己写博记录经验的想法,有不足之处还请各位前辈不吝指点!
背景
最近在项目交付测试的过程中,经常有应用在一顿操作之后界面就一直在转,最后报了无法获取数据的错误,查看日志发现有报错,其中关键点在于java.lang.OutOfMemoryError:Java heap space,jvm内存溢出,如下图
经过网上一顿搜索,发现有很多可以监测定位的方法,但是由于和自己的业务场景不合,都以失败而告终。最终尝试出一条可以解决自己问题的路径,记录下来以供下次复查。
内容提纲
环境和工具
操作系统:centos7
部署环境:docker/k8s
jdk:1.8.0_191
开发框架:springboot1.5
jvm监测工具:jvisualvm
ssh工具:SecureCRT
步骤
1、 更改Dockerfile配置
更改应用的dockerfile文件,增加java 启动参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/wch,如下:
FROM xxx.com/fcae/jre-mos:latest
MAINTAINER FAE Config Server "fae@xxx.com"
ADD ./target/task-1.0.0.jar app.jar
RUN sh -c 'touch /app.jar'
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV APP_NAME=task
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/wch -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
其中,-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/wch 为新增启动项,补充一下参数介绍:
-XX:+HeapDumpOnOutOfMemoryError :当应用抛出 OutOfMemoryError 时自动生成dump文件
-XX:HeapDumpPath=/home/wch :指定dump文件的存储路径(后缀是.hprof)
2、 docker构建并启动应用
构建应用并启动容器,然后复现内存溢出的场景,发现日志出现内存溢出了,但指定位置的dump文件并未生成。后研究发现,docker 构建的镜像使用的openjdk,和oracle的jdk1.8功能是不一样的,所以最后只能将应用在测试环境使用 java -jar 启动,使用上oracle的jdk,再复现内存溢出的场景,启动命令如下:
java -jar -Xmx512m -Xms256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/wch task-1.0.0.jar
3、 java -jar方式启动应用
启动后复现内存溢出场景,日志如下:
其中,Dumping heap to /home/wch/java_pid25882.hprof 指出dump文件存储到了指定的/home/wch路径下
这里的图中的hprof文件可能和下文的不一致,由于项目的内存溢出问题已解决,问题图片未留档,使用另外一种方式复现了内存溢出的场景,只是想告诉读者,在Java启动项里加上配置后,内存溢出时,会有如上图所示的hprof文件生成日志。
4、 下载dump(.hprof后缀)文件
到指定路径下下载hprof文件,下载方法有很多种,下面列出一种:
将SecureCRT的ftp会话切换到sftp会话,然后执行如下命令:
cd /home/wch 切换到dump文件存储路径
lls 列出对应自己本地存储路径下文件
get -r java_pid2134.hprof 将测试环境下的dump文件下载到本地
5、 使用jvisualvm工具装载dump文件(.hprof 后缀)
jvisualvm工具在jdk安装bin目录下,打开后 选择菜单文件 --> 装入,选择下载下来的hprof文件,打开,如下图:
6、 分析导致内存溢出的线程
在概要 --> 基本信息里可以看到 导致 OutOfMemoryError 异常错误的线程: XNIO-2 task-8
点击异常线程链接 (图中2处) ,跳转到异常线程堆栈信息,如下图,红色的线程是造成内存溢出的线程
在堆栈信息里就可以定位到导致内存溢出的具体业务类了,由该dump文件定位出 是一个查询的接口,数据量500多万,未做分页,可能是 大量数据流进内存,导致内存不足,于是重构查询逻辑加上分页,就未复现出该问题了。
7、分析导致内存溢出的类
若是想分析是哪些类导致了内存溢出,可以继续点击jvisualvm的类视图,如下图:
图中可知byte[] 类占用内存最多,61%的内存,继续往下看,可以看到JDBC的类也占了5%的内存,猜测问题可能跟数据库连接有关。
继续看byte[] 类 应用的实例,在类名上右键,在实例视图中显示,会报下图的错误:
提示是netbeans IDE 配置的内存不足,但我自己又未安装该开发工具,又是一顿艰难搜索,终于找到
解决方案。
找到jvisualvm工具的配置文件,我的路径如下:
D:\Program Files\Java\jdk1.8.0_66\lib\visualvm\etc\visualvm.conf
打开文件可以看到我的启动内存和最大内存只有100m、200m,如下图:
将 参数改成 -J-Xms1024m -J-Xmx2048m
重启jvisualvm工具,再次查看类的实例视图,发现可以正常打开了。
如下图,可以查看类的引用信息。
结论
- 以镜像方式(docker)部署的Java应用,打包的是openjdk,可能有些Java脚本和自己本地开发使用的jdk脚本不一致,导致有些监测方法无法使用
注意事项
暂无
参考文章
VisualVm Error记录