在线上故障定位(尤其是OOM)和性能分析的时候,经常会用到一些文件来帮助我们排除代码问题。这些文件记录了JVM运行期间的内存占用、线程执行等情况,也就是我们常说的dump文件。常用的有Heap Dump和Thread Dump(也叫javacore)。
- Heap Dump:它是一个二进制文件,保存了指定时刻的Java堆栈的快照,一种镜像文件。它反映了某时间点下JVM堆中对象的使用情况。
- Thread Dump:它主要保存的是java应用程序中各线程在某一时间点的运行的位置,即程序执行到哪一个类的哪一个方法哪一个行上。thread dump是一个文本文件,打开后可以看到每一个线程的执行栈,以stacktrace的方式显示。
通常,单个thread dump文件一般来说没什么用处,因为它只是记录了某一个绝对时间点的情况。比较有用的是,线程在一个时间段内的执行情况,所以两个thread dump文件在分析时特别有效。我们可以通过对thread dump的分析可以得到应用是否“卡”在某个点上,某个点运行时间太长,比如数据库查询,长期得不到响应。
这里我们重点说一下,如何使用heap dump进行内存分析。
首先,怎么获取到它呢?有三种方式:
- 使用JDK自带的jmap ①工具来触发,语法为:
$JAVA_HOME/bin/jmap -dump:live,format=b,file=[dump文件名] [PID] ② - 在应用程序启动时配置JVM参数 -XX:+HeapDumpOnOutOfMemoryError,当应用抛出OutOfMemoryError时自动生成dump文件
- 启用hprof功能。启动虚拟机时加入-Xrunhprof:head=site 配置,那么JVM会实时生成java.hprof.txt文件。但该配置会导致jvm运行非常的慢,不适合生产环境。
那如果我们即没有配置JVM参数也未启用hprof功能呢?只能使用选用第一种方式了,这里我们重点说一下要怎么做。
经常使用Linux系统的人知道,使用$JAVA_HOME的定位JDK的安装路径的前提是配置了环境变量$JAVA_HOME,如果没有配置(服务器环境现在通常都是没有配置的),那么就会出现如下情况:
如果使用which指令呢?首先你需要,which java定位到的是java程序的执行路径,并非安装路径。但是如果你仔细观察的话你会发现,which java 这个指令其实定位到了一个链接文件,它链接到的就是安装路径。所以我们可以这么做:
知道了$JAVA_HOME,通过jmap工具就能获取到dump文件了,那么现在dump文件有了,怎么解析它呢?解析heap dump有很多工具可以使用,比如IBM的HeapAnalyzer、JDK自带的jhat工具、Eclipse自带的MemoryAnalyzer插件,这里我们使用HeapAnalyzer。
下载dump文件到Windows本地,IBM下载HeapAnalyzer工具,然后使用如下命令行的方式启动它:
java –Xmx[heapsize] –jar ha<HeapAnalyzer version>.jar <dump-file>
注意:
- 如果dump文件不大的话,-Xmx参数可忽略
- 可以忽略<dump-file>参数,先打开HeapAnalyzer,再File -> Open 导入dump-file文件
界面如图所示:
查看Reference view视图或者打开Analysis -> Tree view视图,查看堆中对象的内存占比。显示的树状图按这种格式展示各个节点:
TotalSize(TotalSize/HeapSize%)[ObjectSize] NumberOfChildObject(Number of root objects) Name
- TotalSize:资源总大小
- TotalSize/HeapSize:堆内存占比
- ObjectSize:对象大小
- NumberOfChildObject:子类个数
- Name Address:内存地址
分析的主要思路是:堆内存占比越高,堆内存消耗越多。通过展开树状图的节点找到内存泄漏的地方,然后逐级向上排查,最后在应用程序中找到调用入口,而后分析程序,确定产生内存泄漏的原因。
注:
① 若要分析thread dump,可以使用jstack工具获取,语法如下:jstack [PID] > [dump文件名],则会将命令执行结果转储到指定的文件中
② 在JDK1.5时,语法为:jmap -heap:format=b [PID],在1.6中:live参数,表示是否指定存活的对象