分享

通过JMX控制在full GC前后做heap dump

 9loong 2012-09-10

有时候我们想知道一个Java程序在一次full GC的时候到底回收了哪些对象。特别是当full GC看起来很频密但系统看起来却又没有内存泄漏的时候,了解究竟是哪些对象引致了这些GC会对调优有帮助。

做了个简单的例子,讲解一种简单的办法在full GC的前后得到heap dump。本文说的办法只能在HotSpot VM上使用;其它JVM要达到同样的目的或许有其它做法,回头有机会再说。
(同样的工作在JRockit或者J9上做似乎都更容易些…

======================================================================

一般获取heap dump的办法

1、jmap
大家最熟悉的办法或许就是JDK自带的命令行工具jmap了。jmap可以在任何时候连接到一个跑在HotSpot VM的Java进程上,根据需要制作HPROF格式的heap dump。
Command prompt代码  收藏代码
  1. jmap -dump:format=b,file=<filename> <pid>  

这是最常用的用法。

在Sun的JDK 5和JDK 1.4.2的后期版本中(JDK 5 update 17和JDK 1.4.2 update 16或更高版本),还可以在启动参数里加上-XX:+HeapDumpOnCtrlBreak,然后通过ctrl + break或者发生SIGQUIT来让VM生成heap dump。
不过这个参数在Sun JDK 6里不存在;JDK6上直接用jmap更方便些,倒也没关系。
JRockit R28倒是支持使用这个参数。

2、JConsoleVisualVMMAT
这几个工具都封装了heap dump的功能,用起来很方便——只要知道如何让这些工具连接到目标进程上。所以它们通常在本地使用很方便,而要连接远程进程就麻烦一些。
还有别的一些工具也有提供生成heap dump功能的,不过我一下想不起来了就只写了上面仨。
GCMV或许也可以吧…呃,刚看了下,不行。还是得在VM里配置参数来生成heap dump。

JConsole:


VisualVM:



Eclipse Memory Analyzer (MAT):


3、JMX的API
Sun JDK通过JMX暴露出HotSpotDiagnosticMXBean,可以用于获取VM信息。它支持dumpHeap(String outputFile, boolean live)操作,让Java程序能直接指定路径和是否只要活对象进行heap dump。使用方法可以参考下面的链接:A. Sundararajan's Weblog: Programmatically dumping heap from Java applications

通过Serviceability Agent API也可以做heap dump。事实上jmap的其中一个模式就是包装了SA API的sun.jvm.hotspot.tools.HeapDumper来完成功能的。

4、JVMTI
很老的版本的JVMTI API里曾经有过heap dump函数,不过后来被去掉了

5、让JVM在一些特定事件发生的时候自动做heap dump
(这就是HotSpot操作起来没有JRockit和J9方便的地方了…)
有时候我们只想在发生OutOfMemoryError的时候让JVM自动生成一个heap dump出来,以便做事后分析。这种时候设置启动参数-XX:+HeapDumpOnOutOfMemoryError即可。参考下面的文章来了解该参数的一些历史:
Alan Bateman: Heap dumps are back with a vengeance!

HotSpot VM支持其它事件触发heap dump么?参考官方文档
引用
Flags marked as manageable are dynamically writeable through the JDK management interface (com.sun.management.HotSpotDiagnosticMXBean API) and also through JConsole. In Monitoring and Managing Java SE 6 Platform Applications, Figure 3 shows an example. The manageable flags can also be set through jinfo -flag.

声明为manageable的参数可以在运行时通过JMX修改。与heap dump相关的有以下4个参数:
hotspot/src/share/vm/runtime/globals.hpp
C++代码  收藏代码
  1. manageable(bool, HeapDumpBeforeFullGC, false,                             \  
  2.         "Dump heap to file before any major stop-world GC")               \  
  3.                                                                           \  
  4. manageable(bool, HeapDumpAfterFullGC, false,                              \  
  5.         "Dump heap to file after any major stop-world GC")                \  
  6.                                                                           \  
  7. manageable(bool, HeapDumpOnOutOfMemoryError, false,                       \  
  8.         "Dump heap to file when java.lang.OutOfMemoryError is thrown")    \  
  9.                                                                           \  
  10. manageable(ccstr, HeapDumpPath, NULL,                                     \  
  11.         "When HeapDumpOnOutOfMemoryError is on, the path (filename or"    \  
  12.         "directory) of the dump file (defaults to java_pid<pid>.hprof"    \  
  13.         "in the working directory)")                                      \  

可以看到,除了HeapDumpOnOutOfMemoryError之外,还有HeapDumpBeforeFullGCHeapDumpAfterFullGC参数,分别用于指定在full GC之前与之后生成heap dump。

顺带一提,前面VisualVM的截图里“Disable Heap Dump on OOME”的功能,就是通过HotSpotDiagnosticMXBean将HeapDumpOnOutOfMemoryError参数设置为false来实现的。

======================================================================

通过JMX API在full GC前后生成heap dump的例子

原始代码放在这里了:https://gist.github.com/978336

很简单,就是演示了:
·获取HotSpotDiagnosticMXBean;
·通过它上面的setVMOption(String name, String value)方法修改HeapDumpBeforeFullGCHeapDumpAfterFullGC参数为true;
·触发一次full GC;
·将VM参数恢复为false。

为了方便,例子用Groovy来写。要在Groovy Shell中看到GC的日志,可以设置环境变量JAVA_OPTIONS=-XX:+PrintGCDetails,或者是在当前目录放一个.hotspotrc来配置这个参数;我是用的后者。

具体代码:
Groovysh代码  收藏代码
  1. $ groovysh  
  2. [GC [PSYoungGen: 14016K->1312K(16320K)] 14016K->1312K(53696K), 0.0111510 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]   
  3. [GC [PSYoungGen: 15328K->2272K(30336K)] 15328K->4832K(67712K), 0.0286280 secs] [Times: user=0.02 sys=0.03, real=0.03 secs]   
  4. Groovy Shell (1.7.7, JVM: 1.6.0_25)  
  5. Type 'help' or '\h' for help.  
  6. ----------------------------------------------------------------------------------------------------------------------------  
  7. groovy:000> import java.lang.management.ManagementFactory  
  8. ===> [import java.lang.management.ManagementFactory]  
  9. groovy:000> import com.sun.management.HotSpotDiagnosticMXBean  
  10. ===> [import java.lang.management.ManagementFactory, import com.sun.management.HotSpotDiagnosticMXBean]  
  11. groovy:000>   
  12. groovy:000> HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"  
  13. ===> com.sun.management:type=HotSpotDiagnostic  
  14. groovy:000> server = ManagementFactory.platformMBeanServer  
  15. [GC [PSYoungGen: 30304K->2288K(30336K)] 32864K->8856K(67712K), 0.0643130 secs] [Times: user=0.16 sys=0.01, real=0.07 secs]   
  16. ===> com.sun.jmx.mbeanserver.JmxMBeanServer@7297e3a5  
  17. groovy:000> bean = ManagementFactory.newPlatformMXBeanProxy(server, HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean)  
  18. ===> MXBeanProxy(com.sun.jmx.mbeanserver.JmxMBeanServer@7297e3a5[com.sun.management:type=HotSpotDiagnostic])  
  19. groovy:000> bean.setVMOption('HeapDumpBeforeFullGC''true')  
  20. ===> null  
  21. groovy:000> bean.setVMOption('HeapDumpAfterFullGC''true')  
  22. ===> null  
  23. groovy:000> System.gc()  
  24. [GC [PSYoungGen: 10460K->2288K(58368K)] 17028K->9639K(95744K), 0.0166920 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]   
  25. [Heap Dump: Dumping heap to java_pid16836.hprof ...  
  26. Heap dump file created [20066598 bytes in 0.347 secs]  
  27. 0.3514030 secs][Full GC (System) [PSYoungGen: 2288K->0K(58368K)] [PSOldGen: 7351K->9621K(37376K)] 9639K->9621K(95744K) [PSPermGen: 18626K->18626K(37824K)], 0.1324840 secs] [Times: user=0.12 sys=0.02, real=0.14 secs]   
  28. [Heap DumpDumping heap to java_pid16836.hprof.1 ...  
  29. Heap dump file created [20013677 bytes in 0.340 secs]  
  30. 0.3398950 secs]===> null  
  31. groovy:000> bean.setVMOption('HeapDumpBeforeFullGC''false')  
  32. ===> null  
  33. groovy:000> bean.setVMOption('HeapDumpAfterFullGC''false')  
  34. ===> null  
  35. groovy:000> quit  
  36. Heap  
  37.  PSYoungGen      total 58368K, used 9250K [0x00000000edc000000x00000000f17400000x0000000100000000)  
  38.   eden space 56064K, 16% used [0x00000000edc00000,0x00000000ee5089b0,0x00000000f12c0000)  
  39.   from space 2304K, 0% used [0x00000000f1500000,0x00000000f1500000,0x00000000f1740000)  
  40.   to   space 2304K, 0% used [0x00000000f12c0000,0x00000000f12c0000,0x00000000f1500000)  
  41.  PSOldGen        total 37376K, used 9621K [0x00000000c94000000x00000000cb8800000x00000000edc00000)  
  42.   object space 37376K, 25% used [0x00000000c9400000,0x00000000c9d65410,0x00000000cb880000)  
  43.  PSPermGen       total 37824K, used 18758K [0x00000000c42000000x00000000c66f00000x00000000c9400000)  
  44.   object space 37824K, 49% used [0x00000000c4200000,0x00000000c5451ba8,0x00000000c66f0000)  

这样就得到了 java_pid16836.hprof 与 java_pid16836.hprof.1 两个heap dump文件。

把第二个heap dump文件改名为 java_pid16836.1.hprof 之后,用MAT打开这两个heap dump,在第一个文件的histogram试图下可以看到


目前MAT只支持histogram试图中比较两个heap dump。
点击上方工具条最右边的“<->”按钮,并选上第二个heap dump文件之后,可以看到:


这样就能很方便的得知这次full GC当中到底收集了多少个什么类型的对象。
实际效果跟手动用jmap -histo比较差不多,不过要精确的在full GC前后手动做些操作不是件简单的事情。

或许会有人想说,为啥MAT不能直接把具体是哪些对象被收集了显示出来呢?
这功能不好做。GC的时候对象可能会被移动,也就是说不能通过地址来将full GC前后的两个heap dump里的记录关联到一起;而HPROF格式也没有记录足够信息让多个heap dump之间能建立起联系。
结果能很方便做比较的就只有按类型做的统计。通常这也能提供有用的头绪去进一步做分析了。

P.S. 如果一个HPROF的heap dump是在开了压缩指针的64位JVM上生成的,那么用MAT查看的时候,里面显示的Shallow Heap和Retained Heap数据都会是错误的。因为HPROF格式只能分辨是32位还是64位的,却没有记录有没有开压缩指针、每个对象实际的大小是多少。这种条件下请不要 相信MAT(或其它分析HPROF格式的heap dump的工具)显示的对象大小。

(###)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多