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

57 篇文章 0 订阅
26 篇文章 0 订阅
后一篇:[url=http://rednaxelafx.iteye.com/blog/1049240]通过jinfo工具在full GC前后做heap dump[/url]

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

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

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

[size=small][b]一般获取heap dump的办法[/b][/size]

[b]1、[url=http://download.oracle.com/javase/6/docs/technotes/tools/share/jmap.html]jmap[/url][/b]
大家最熟悉的办法或许就是JDK自带的命令行工具jmap了。jmap可以在任何时候连接到一个跑在HotSpot VM的Java进程上,根据需要制作[url=http://hg.openjdk.java.net/jdk6/jdk6/jdk/file/tip/src/share/demo/jvmti/hprof/manual.html]HPROF格式[/url]的heap dump。
jmap -dump:format=b,file=<filename> <pid>

这是最常用的用法。

在Sun的JDK 5和JDK 1.4.2的后期版本中(JDK 5 update 17和JDK 1.4.2 update 16或更高版本),还可以在启动参数里加上[url=http://blogs.oracle.com/poonam/entry/heap_dump_using_xx_heapdumponctrlbreak][b]-XX:+HeapDumpOnCtrlBreak[/b][/url],然后通过[b]ctrl + break[/b]或者发生SIGQUIT来让VM生成heap dump。
不过这个参数在Sun JDK 6里不存在;JDK6上直接用jmap更方便些,倒也没关系。
[url=http://download.oracle.com/docs/cd/E15289_01/doc.40/e15062/optionxx.htm]JRockit R28[/url]倒是支持使用这个参数。

[b]2、[url=http://download.oracle.com/javase/6/docs/technotes/tools/share/jconsole.html]JConsole[/url]、[url=http://visualvm.java.net/]VisualVM[/url]、[url=www.eclipse.org/mat/]MAT[/url][/b]
这几个工具都封装了heap dump的功能,用起来很方便——只要知道如何让这些工具连接到目标进程上。所以它们通常在本地使用很方便,而要连接远程进程就麻烦一些。
还有别的一些工具也有提供生成heap dump功能的,不过我一下想不起来了就只写了上面仨。
[url=http://www.ibm.com/developerworks/java/jdk/tools/gcmv/]GCMV[/url]或许也可以吧…呃,刚看了下,不行。还是得在VM里配置参数来生成heap dump。

JConsole:
[img]http://dl.iteye.com/upload/attachment/483670/70f7efd1-2cc7-3d12-8396-64bcfae7b5fe.png[/img]

VisualVM:
[img]http://dl.iteye.com/upload/attachment/483666/ca7a42b9-a69f-34ae-b7de-a00d5d0e0829.png[/img]
[img]http://dl.iteye.com/upload/attachment/483668/9ee1e212-93c2-3cb4-98f8-e27af4d2904f.png[/img]

Eclipse Memory Analyzer (MAT):
[img]http://dl.iteye.com/upload/attachment/483673/f1140895-7062-312c-a5cd-55dd39a48446.png[/img]

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

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

[b]4、JVMTI[/b]
很老的版本的JVMTI API里曾经有过heap dump函数,[url=http://java.sun.com/developer/technicalArticles/Programming/jvmti/]不过后来被去掉了[/url]。

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

HotSpot VM支持其它事件触发heap dump么?参考[url=http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html]官方文档[/url]:
[quote]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.[/quote]
声明为manageable的参数可以在运行时通过JMX修改。与heap dump相关的有以下4个参数:
[url=http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/tip/src/share/vm/runtime/globals.hpp]hotspot/src/share/vm/runtime/globals.[/url]hpp
manageable(bool, HeapDumpBeforeFullGC, false,                             \
"Dump heap to file before any major stop-world GC") \
\
manageable(bool, HeapDumpAfterFullGC, false, \
"Dump heap to file after any major stop-world GC") \
\
manageable(bool, HeapDumpOnOutOfMemoryError, false, \
"Dump heap to file when java.lang.OutOfMemoryError is thrown") \
\
manageable(ccstr, HeapDumpPath, NULL, \
"When HeapDumpOnOutOfMemoryError is on, the path (filename or" \
"directory) of the dump file (defaults to java_pid<pid>.hprof" \
"in the working directory)") \

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

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

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

[size=small][b]通过JMX API在full GC前后生成heap dump的例子[/b][/size]

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

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

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

具体代码:
$ groovysh
[GC [PSYoungGen: 14016K->1312K(16320K)] 14016K->1312K(53696K), 0.0111510 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 15328K->2272K(30336K)] 15328K->4832K(67712K), 0.0286280 secs] [Times: user=0.02 sys=0.03, real=0.03 secs]
Groovy Shell (1.7.7, JVM: 1.6.0_25)
Type 'help' or '\h' for help.
----------------------------------------------------------------------------------------------------------------------------
groovy:000> import java.lang.management.ManagementFactory
===> [import java.lang.management.ManagementFactory]
groovy:000> import com.sun.management.HotSpotDiagnosticMXBean
===> [import java.lang.management.ManagementFactory, import com.sun.management.HotSpotDiagnosticMXBean]
groovy:000>
groovy:000> HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"
===> com.sun.management:type=HotSpotDiagnostic
groovy:000> server = ManagementFactory.platformMBeanServer
[GC [PSYoungGen: 30304K->2288K(30336K)] 32864K->8856K(67712K), 0.0643130 secs] [Times: user=0.16 sys=0.01, real=0.07 secs]
===> com.sun.jmx.mbeanserver.JmxMBeanServer@7297e3a5
groovy:000> bean = ManagementFactory.newPlatformMXBeanProxy(server, HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean)
===> MXBeanProxy(com.sun.jmx.mbeanserver.JmxMBeanServer@7297e3a5[com.sun.management:type=HotSpotDiagnostic])
groovy:000> bean.setVMOption('HeapDumpBeforeFullGC', 'true')
===> null
groovy:000> bean.setVMOption('HeapDumpAfterFullGC', 'true')
===> null
groovy:000> System.gc()
[GC [PSYoungGen: 10460K->2288K(58368K)] 17028K->9639K(95744K), 0.0166920 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
[Heap Dump: Dumping heap to java_pid16836.hprof ...
Heap dump file created [20066598 bytes in 0.347 secs]
, 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]
[Heap DumpDumping heap to java_pid16836.hprof.1 ...
Heap dump file created [20013677 bytes in 0.340 secs]
, 0.3398950 secs]===> null
groovy:000> bean.setVMOption('HeapDumpBeforeFullGC', 'false')
===> null
groovy:000> bean.setVMOption('HeapDumpAfterFullGC', 'false')
===> null
groovy:000> quit
Heap
PSYoungGen total 58368K, used 9250K [0x00000000edc00000, 0x00000000f1740000, 0x0000000100000000)
eden space 56064K, 16% used [0x00000000edc00000,0x00000000ee5089b0,0x00000000f12c0000)
from space 2304K, 0% used [0x00000000f1500000,0x00000000f1500000,0x00000000f1740000)
to space 2304K, 0% used [0x00000000f12c0000,0x00000000f12c0000,0x00000000f1500000)
PSOldGen total 37376K, used 9621K [0x00000000c9400000, 0x00000000cb880000, 0x00000000edc00000)
object space 37376K, 25% used [0x00000000c9400000,0x00000000c9d65410,0x00000000cb880000)
PSPermGen total 37824K, used 18758K [0x00000000c4200000, 0x00000000c66f0000, 0x00000000c9400000)
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试图下可以看到
[img]http://dl.iteye.com/upload/attachment/483662/b0321b6a-4ded-30c8-8591-cf2a8e9b70da.png[/img]

目前MAT只支持histogram试图中比较两个heap dump。
点击上方工具条最右边的“<->”按钮,并选上第二个heap dump文件之后,可以看到:
[img]http://dl.iteye.com/upload/attachment/483664/91d433e8-b4e9-3939-9fb8-2c2f85be7e19.png[/img]

这样就能很方便的得知这次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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值