4.3 jmap与jhat
jmap(Java Memory Map)命令可以获得运行中的jvm的堆的快照,从而可以离线分析堆,以检查内存泄漏,检查一些严重影响性能的大对象的创建,检查系统中什么对象最多,各种对象所占内存的大小。
命令语法:
jmap [option] <pid> |
其中pid可以通过jps命令获取,option可选项如下:
-heap:打印jvm 内存整体使用情况
-histo[:live]:打印jvm heap的直方图。其输出信息包括类名,对象数量,对象占用大小。
-dump:<dump-options>:dump java内存到二进制文件中。
dump-options:
live 是否只dump当前存活的对象,如果不指定,将会dump所有的对象,包括待回收的对象。
format=b 文件的格式
file=<file> 指定文件的输出位置
-permstat:打印permanent generation heap情况
上述命令中的-histo和-dumo都包含一个live选项,如果指定live,在统计前会进行full gc,因此不加live的堆大小要大于加live堆的大小。
命令使用
1、打印jvm 内存整体使用情况
jmap -heap pid
可以观察到New Generation(Eden Space,From Space,To Space),tenured generation,Perm Generation的内存使用情况
[root@www wangxiaoxiao]# jmap -heap 12446 Attaching to process ID 12446, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.65-b01 using parallel threads in the new generation. using thread-local object allocation. Concurrent Mark-Sweep GC Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 536870912 (512.0MB) NewSize = 134152192 (127.9375MB) MaxNewSize = 134217728 (128.0MB) OldSize = 65536 (0.0625MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB) Heap Usage: New Generation (Eden + 1 Survivor Space): capacity = 120782848 (115.1875MB) used = 80598816 (76.86502075195312MB) free = 40184032 (38.322479248046875MB) 66.73034899789745% used Eden Space: capacity = 107413504 (102.4375MB) used = 75663840 (72.15866088867188MB) free = 31749664 (30.278839111328125MB) 70.44164577295606% used From Space: capacity = 13369344 (12.75MB) used = 4934976 (4.70635986328125MB) free = 8434368 (8.04364013671875MB) 36.91262637867647% used To Space: capacity = 13369344 (12.75MB) used = 0 (0.0MB) free = 13369344 (12.75MB) 0.0% used concurrent mark-sweep generation: capacity = 198795264 (189.5859375MB) used = 138417264 (132.00498962402344MB) free = 60378000 (57.58094787597656MB) 69.62804908672271% used 44470 interned Strings occupying 5924248 bytes. |
2、查看类名,对象数量,对象占用大小
jmap -histo[:live] pid
可以观察heap中所有对象的情况(heap中所有生存的对象的情况)。包括对象数量和所占空间大小。
[root@www wangxiaoxiao]# jmap -histo 12446 | more num #instances #bytes class name ---------------------------------------------- 1: 648278 81563800 [C 2: 252059 30068112 [B 3: 502067 12049608 java.lang.String 4: 53315 8825728 [I 5: 114705 8823824 [Ljava.lang.Object; 6: 210107 6723424 java.util.HashMap$Node 7: 60821 5352248 java.lang.reflect.Method 8: 62414 5060664 [S 9: 54688 4812544 java.util.jar.JarEntry 10: 131308 4201856 org.springframework.boot.loader.util.AsciiBytes 11: 28337 3420512 [Ljava.util.HashMap$Node; 12: 59001 3304056 org.springframework.boot.loader.jar.JarEntryData 13: 87337 2794784 java.util.concurrent.ConcurrentHashMap$Node 14: 69859 2794360 java.lang.ref.SoftReference 15: 36298 2613456 java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask 16: 22683 2497536 java.lang.Class 17: 60280 2411200 java.util.LinkedHashMap$Entry 18: 27206 1741184 java.net.URL 19: 56643 1359432 java.util.concurrent.ConcurrentSkipListMap$Node 20: 22687 1270472 java.util.LinkedHashMap 21: 23984 1151232 java.util.HashMap 22: 15122 1088784 org.springframework.boot.loader.jar.JarURLConnection 23: 26521 1060840 java.util.TreeMap$Entry 24: 57292 916672 java.lang.Object 25: 27960 894720 java.util.Hashtable$Entry 26: 36242 869808 java.util.concurrent.Executors$RunnableAdapter 27: 931 838160 [Ljava.util.concurrent.ConcurrentHashMap$Node; |
输出说明:
instances列:表示当前类有多少个实例。
bytes列:说明当前类的实例总共占用了多少个字节
class name列:表示的就是当前类的名称,class name 解读:
B代表byte
C代表char
D代表double
F代表float
I代表int
J代表long
Z代表boolean
前边有[代表数组,[I 就相当于int[]
对象用[L+类名表示
手工dump内存到本地文件
jmap -dump:file=heap.map 12446
[root@www wangxiaoxiao]# jmap -dump:file=heap.map 12446 Dumping heap to /home/wangxiaoxiao/heap.map ... Heap dump file created |
jhat(Java Heap Analyse Tool)
用途:是用来分析java堆的命令,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言
使用:
[root@www wangxiaoxiao]# jhat heap.map #heap.map是通过jmap -dump:file=heap.map pid命令导出 Reading from heap.map... Dump file created Tue Feb 14 10:50:34 CST 2017 Snapshot read, resolving... Resolving 2570075 objects... Chasing references, expect 514 dots ........................................................................................................................... ................................................................................................................ Eliminating duplicate references ............................................................................................................................. ................................................................................................................ ................................................................................................................. Snapshot resolved. Started HTTP server on port 7000 Server is ready. |
访问 http://localhost:7000,就可以查看详细的内存信息
关于jhat,不打算多说,因为一般实际开发中,很少使用jhat来直接对内存dump文件进行分析。下面介绍一个MAT,这个用的比较多。
MAT(Memory Analyzer Tool)
MAT是一个eclipse的插件,上手起来比较快。它能够快速的分析dump文件,可以直观的看到各个对象在内存占用的量大小,以及类实例的数量,对象之间的引用关系,找出对象的GC Roots相关的信息,此外还能生成内存泄露报表,疑似泄露大对象的报表等等。
安装MAT
可以选择eclipse插件的方式安装 http://download.eclipse.org/mat/1.3/update-site/ 也可以选择单独MAT程序下载安装 |
内存溢出时,自动保存dump文件
前面是手工导出内存dump映射文件,如果应用已经在线上运行,为了能获取应用出现内存溢出时获得heap dump文件,以便在之后分析,可以在JVM启动时指定参数:-XX:+HeapDumpOnOutOfMemoryError,JVM会在遇到OutOfMemoryError时保存一个“堆转储快照”,并将其保存在一个文件中。 文件路径通过-XX:HeapDumpPath指定。
案例
public class OomDemo { public static void main(String[] args) { Map<String,Long> map=new HashMap<String, Long>(); for (long i = 0; i < Long.MAX_VALUE; i++) { map.put(i+"",i); } } }
设置虚拟机参数为:-Xmx40m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\Java\dump.hprof
执行程序,很快会抛出异常:
java.lang.OutOfMemoryError: GC overhead limit exceeded Dumping heap to D:\Java\dump ... Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded Heap dump file created [55940678 bytes in 0.556 secs] at java.lang.StringBuilder.toString(StringBuilder.java:405) at oom.OomDemo.main(OomDemo.java:13) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) |
生成了文件之后,就可以通过MAT打开来进行分析
File->Open Heap Dump->选择文件
弹出如下界面:
选中第一个,点击finish,出现以下界面:
可以看到,提示在主线程(main)的一个本地变量中使用了98.77%的内存,而这个本地变量就是java.util.HashMap$Entry[]。