JVM Memory Analysis
Table of Contents
1. OutOfMemoryError 错误简介
在 Java 中, 开发者不用直接控制程序运行内存, JVM 有垃圾回收器对内存区域管理。尽管如此,OutOfMemoryError 是常见的 Java 错误,有多种不同原因可以导致 java.lang.OutOfMemoryError,如:
java.lang.OutOfMemoryError: Java heap space java.lang.OutOfMemoryError: GC Overhead limit exceeded java.lang.OutOfMemoryError: Requested array size exceeds VM limit ......
不同类型的 OutOfMemoryError,有不同原因及解决办法,可参考:Java Platform, Standard Edition Troubleshooting Guide
1.1. OutOfMemoryError 实例程序
下面是一个演示程序,它会导致 OutOfMemoryError:
import java.util.ArrayList; import java.util.List; // -Xms40m -Xmx40m -XX:+HeapDumpOnOutOfMemoryError public class OutOfMemoryTest { static private List<MyClassA> list = new ArrayList<MyClassA>(); public static void main(String[] args) { while (true) { // 不停地往list中增加MyClassA对象 list.add(new MyClassA()); } } } class MyClassA { }
上面程序的可能运行结果:
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid33381.hprof ... Heap dump file created [60995402 bytes in 0.252 secs] 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.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at OutOfMemoryTest.main(OutOfMemoryTest.java:10)
1.2. 实例:用 jmap 检查内存占用基本情况
用 jmap -heap <pid>
可检查内存占用基本情况,如:
$ jmap -heap 27118 Attaching to process ID 27118, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.112-b16 using thread-local object allocation. Parallel GC with 4 thread(s) Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 4294967296 (4096.0MB) NewSize = 89128960 (85.0MB) MaxNewSize = 1431306240 (1365.0MB) OldSize = 179306496 (171.0MB) 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 = 213909504 (204.0MB) used = 0 (0.0MB) free = 213909504 (204.0MB) 0.0% used From Space: capacity = 254279680 (242.5MB) used = 254279680 (242.5MB) free = 0 (0.0MB) 100.0% used To Space: capacity = 365428736 (348.5MB) used = 0 (0.0MB) free = 365428736 (348.5MB) 0.0% used PS Old Generation capacity = 1231552512 (1174.5MB) used = 1157770520 (1104.1360092163086MB) free = 73781992 (70.3639907836914MB) 94.0090259017717% used 734 interned Strings occupying 49224 bytes.
1.3. 实例:用 jmap 检查哪些 Java 实例占用较大内存
用 jmap -histo:live <pid>
可分析 Java 实例数和实例所占内存大小,如:
$ jmap -histo:live 28208 num #instances #bytes class name ---------------------------------------------- 1: 64213348 1027413568 MyClassA 2: 524 280390160 [Ljava.lang.Object; 3: 1030 94200 [C 4: 482 54968 java.lang.Class 5: 10 25024 [B 6: 1018 24432 java.lang.String 7: 79 5688 java.lang.reflect.Field 8: 256 4096 java.lang.Integer ......
注:“[C”代表字符数组,“[B”表示布尔数组。
2. Eclispe MAT(Memory Analyzer Tool)
Eclispe MAT(Memory Analyzer Tool) 是一个强大的 Java 内存分析工具,强烈推荐使用。
Eclispe MAT 即是一个 Eclipse 插件,也提供了单独的安装程序。如果是以 Eclipse 插件形式安装,则点击“Window > Perspective > Open Perspective > Other ... > Memory Analysis”可以找到它。
参考:
Basic Tutorial: http://help.eclipse.org/kepler/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fgettingstarted%2Fbasictutorial.html
2.1. 生成 Heap dump 文件
使用 Eclispe MAT,首先你需要有 Heap dump 文件,有多种方法可以生成 Heap dump 文件。如:
方法一:启动 jvm 时指定下面选项: -XX:+HeapDumpOnOutOfMemoryError
,使用这种方法只有发生 OutOfMemoryError 错误时才会产生 heap dump 文件。
方法二(推荐方式),使用 jcmd <pid> GC.heap_dump <full_path_of_file>
方法三:使用 jmap -dump:format=b,file=myfile1.hprof <pid>
注: jcmd
还有很多其它功能,使用 jcmd <pid> help
可查看 jcmd
的所有支持命令。
2.2. Dominator Tree
The dominator tree displays the biggest objects in the heap dump.
Figure 1: Dominator Tree in Eclispe MAT
2.2.1. Shallow Heap vs. Retained Heap
在 Dominator Tree 中,会显示 Shallow Heap 和 Retained Heap 的大小(如图 1 所示)。它们的含义分别如下:
Shallow Heap:对象自身占用的内存大小,不包括它引用的对象。
Retained Heap:当前对象大小加上当前对象可直接或间接引用到的对象的大小总和。也就是说, Retained Heap 就是当前对象被 GC 后,从 Heap 上总共能释放掉的内存。
在分析 Java 内存占用过多的问题时,观察 Retained Heap 的大小更有意义,Dominator Tree 中默认就是把 Retained Heap 较大的记录排在前面。
2.3. Path to GC Roots
我们可以通过“Path to GC Roots”(如图 2 所示)功能查看可疑对象到 GC Roots 的引用链。于是就能找到可疑对象是通过怎样的路径与 GC Roots 相关联并导致垃圾回收器无法自动回收它们的,这样可以比较准确地定位出泄露代码的位置。
Figure 2: Path to GC Roots