Java程序运行中常常会遇到各种关于内存的问题,例如内存泄漏、内存溢出、内存使用率太高等问题,如果没有合适的工具和方法,则定位问题时常常感觉难以入手。本文介绍如何使用Jmap配合MAT进行Java堆内存分析,快速定位问题。
Jmap是Java提供的用于打印进程的堆内存信息的命令,使用这个命令可以查看堆内存的具体使用情况,打印一个进程、可执行core文件、远程debug服务的堆内存,导出堆转储文件,用于离线分析。
当进程运行于一个64位的虚拟机的话,需要加上-J-d64参数
$ jmap -heap 71664
Attaching to process ID 71664, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 27262976 (26.0MB)
NewSize = 8912896 (8.5MB)
MaxNewSize = 8912896 (8.5MB)
OldSize = 18350080 (17.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 6815744 (6.5MB)
used = 2982392 (2.8442306518554688MB)
free = 3833352 (3.6557693481445312MB)
43.75739464393029% used
From Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
PS Old Generation
capacity = 18350080 (17.5MB)
used = 0 (0.0MB)
free = 18350080 (17.5MB)
0.0% used
1779 interned Strings occupying 158984 bytes.
$ jmap -dump:live,format=b,file=71664.hprof 71664
为了模拟使用MAT进行堆内存分析地情况,在此特意构建一个内存溢出的情况,导出这个进程的堆转储文件,代码如下:
public class OutOfMemory {
public static void main(String[] args) throws InterruptedException {
Vector v=new Vector(5);
for (int i=1;i<1000000; i++)
{
Object o=new Object();
v.add(o);
}
System.out.println("finished");
}
}
导出堆转储文件,除了以上使用jmap命令以外,也可以使用JVM参数指定当发生OutOfMemory时自动导出堆转储文件。例如以上代码执行时加上以下JVM参数
-Xms25m -Xmx25m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails
执行此程序,导出了堆转储文件java_pid71932.hprof
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid71932.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.Vector.grow(Vector.java:266)
at java.util.Vector.ensureCapacityHelper(Vector.java:246)
at java.util.Vector.add(Vector.java:782)
at tech.liujintao.leetcode.OutOfMemory.main(OutOfMemory.java:12)
Heap dump file created [12430601 bytes in 0.028 secs]
MAT(Memory Analyzer Tool)是Eclipse的一个插件,也提供单独运行的版本。主要用于进行堆转储文件的分析,其使用方便简单、功能强大,能够清晰地展示堆内存中各类对象的大小、所占的比例、可能出现内存问题的报表、线程栈等信息,为问题定位提供强大地辅助。
打开MAT软件,点击"File"->“Open Heap Dump”,选择对应的hprof文件载入堆转储文件,选择Leak Suspect,进入Overview页面
通过上图可以看到当前占用的总的堆内存为6.8M,其中最大的对象占用的内存为6.3M,下面还有多个功能模块:Actions、Reports和Step By Step。
点击Leak Suspects,这个报表是MAT分析出来的可能导致内存泄漏、内存溢出的问题点分析,如下图所示
图上显示,main线程保持了本地变量,这个变量占用了91.64%的堆内存,此对象对应的类是java.lang.Object,由system class loader加载,很明显,此对象占用了那么大的内存并且没有被回收,可能存在问题。
那么是哪个类加载了这个对象呢?这个时候选择点击"See stacktrace"查看栈轨迹
可以看到问题点代码是OutOfMemory.java类第12行,那么就对应查看对应类的代码排查即可。
点击"Details"可以查看更加详细的内容,例如可以查看到问题点代码的最短路径
可以看到java.lang.Object是由main线程中Vector对象保持的。其中Shallow Heap和Retained Heap展示了对象的大小。
Details中还有支配树视图Dominator Tree,用于查看受当前对象支配的对象中哪个占用的Retained Heap比较大,例如下图展示了当前对象“java.lang.Thread @ 0xffc59ab0 main”支配下的对象占用的堆内存情况。
由于加载的对象很多,所以为了方便查看,根据类进行了堆内存的分类
该视图以Class类的维度展示每个Class类的实例存在的个数、 占用的Shallow Heap和 Retained Heap 大小,可以用于协助判断哪些实例对象大量驻留于堆内存中,为定位问题提供参考。
更加详细地,可以右键某个对象,选择"List objects"=> “with outgoing references"或者"with incoming references”,前者代表的是当前对象引用了哪些对象,后者是当前对象被哪些对象引用了,这就方便进行追踪。
在 Histogram视图 和 Domiantor Tree视图时,默认是按照对象的维度进行分组,点击工具栏的分组功能,可以按照类、ClassLoader、包进行分组,更加方便定位到问题代码,具体如下图所示
Thread视图直观地展示出当前所有的线程,包括线程的名字、线程所占用的堆内存的大小,线程下的本地变量、classloader等信息,具体如下图所示,除了进行内存分析,还支持堆线程的分析,功能相当强大。
通过以上视图,已经能够为堆内存地分析提供极多的参考信息,快速定位内存溢出等问题。