通过JMX控制在full GC前后做heap dump
后一篇:通过jinfo工具在full GC前后做heap dump
有时候我们想知道一个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。
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么?参考官方文档:
可以看到,除了HeapDumpOnOutOfMemoryError之外,还有HeapDumpBeforeFullGC与HeapDumpAfterFullGC参数,分别用于指定在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)方法修改HeapDumpBeforeFullGC与HeapDumpAfterFullGC参数为true;
触发一次full GC;
将VM参数恢复为false。
为了方便,例子用Groovy来写。要在Groovy Shell中看到GC的日志,可以设置环境变量JAVA_OPTIONS=-XX:+PrintGCDetails,或者是在当前目录放一个.hotspotrc来配置这个参数;我是用的后者。
具体代码:
目前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的工具)显示的对象大小。
时间上应该今年1月中旬吧。其实用的就是这个演示稿,只不过没有足够时间把那么多内容都塞进来。
印象中那次分享后我是发过这个演示稿给hongjiang的…呃,难道漏了。
agapple 写道还有一个问题,就是在验证一些逃逸优化时,有些jvm参数用不了,比如-XX:printInlining,-XX:printAssembly,jdk用的是1.6.11和jdk1.6.18
-XX:+PrintInlining在product build的Sun JDK上可以是可以用,但什么也显示不出来。要在debug build(debug或者fastdebug)上才有意义。
-XX:PrintAssembly的使用请参考这篇文章:JVM 反汇编动态运行代码
简单来说,如果在声明那些VM参数的地方,写着是product、product_pd、diagnostic或者manageable的,那就是在平时用的product build里可以用的。其它都至少得在fastdebug build里才可以用。 9 楼 RednaxelaFX 2011-05-19 agapple 写道因为在sun网站没找到一个地方能很全面的介绍这些参数内容
对了,Sun的网站上以前有人放过一份非官方的参数列表的,也可以参考:
http://blogs.sun.com/watt/resource/jvm-options-list.html
不过比较老了…