HPROF是一个每个JDK发行版都会内置的堆和CPU分析工具。它是一个使用JVM TI和JVM交互的DLL。该工具可以将分析信息写入文件或者以ASCII或者二进制格式写入socket,这些信息将来可以用前端分析工具来处理。
HPROF工具能展现CPU使用、堆分配统计和监视内容分析。此外,它也能报告JVM中所有监视者和线程的完整堆dump和状态。就诊断的问题而言,HPROF在分析性能问题、锁争用、内存泄漏和其他问题时有用。
除了HPROF库之外,JDK还包含HPROF作为JVM TI演示的源码。代码位置在$JAVA_HOME/demo/jvmti/hprof
HPROF工具像下面那样调用:java -agentlib:hprof ToBeProfiledClass
取决于分析的请求类型,HPROF指示JVM去发送它到相关的事件。工具然后处理事件数据到分析信息中。例如,以下命令获取堆分配分析:java -agentlib:hprof=heap=sites ToBeProfiledClass
当HPROF代理以help选项提供时将会打印完整的选项列表:java -agentlib:hprof=help
C:\Users\lhl>java -agentlib:hprof=help
HPROF: Heap and CPU Profiling Agent (JVMTI Demonstration Code)
hprof usage: java -agentlib:hprof=[help]|[<option>=<value>, ...]
Option Name and Value Description Default
--------------------- ----------- -------
heap=dump|sites|all heap profiling all
cpu=samples|times|old CPU usage off
monitor=y|n monitor contention n
format=a|b text(txt) or binary output a
file=<file> write data to file java.hprof[{.txt}]
net=<host>:<port> send data over a socket off
depth=<size> stack trace depth 4
interval=<ms> sample interval in ms 10
cutoff=<value> output cutoff point 0.0001
lineno=y|n line number in traces? y
thread=y|n thread in traces? n
doe=y|n dump on exit? y
msa=y|n Solaris micro state accounting n
force=y|n force output to <file> y
verbose=y|n print messages about dumps y
Obsolete Options
----------------
gc_okay=y|n
Examples
--------
- Get sample cpu information every 20 millisec, with a stack depth of 3:
java -agentlib:hprof=cpu=samples,interval=20,depth=3 classname
- Get heap usage information based on the allocation sites:
java -agentlib:hprof=heap=sites classname
Notes
-----
- The option format=b cannot be used with monitor=y.
- The option format=b cannot be used with cpu=old|times.
- Use of the -Xrunhprof interface can still be used, e.g.
java -Xrunhprof:[help]|[<option>=<value>, ...]
will behave exactly the same as:
java -agentlib:hprof=[help]|[<option>=<value>, ...]
Warnings
--------
- This is demonstration code for the JVMTI interface and use of BCI,
it is not an official product or formal part of the JDK.
- The -Xrunhprof interface will be removed in a future release.
- The option format=b is considered experimental, this format may change
in a future release.
默认情况下,堆分析信息(site和dump)被写入当前目录的java.hprof.txt(ascii)
输出通常在JVM退出时生成,尽管这可以通过设置doe选项到n(doe=n)来禁用。此外,当ctrl+\或者ctrl+break按下时会产生一个分析文件。在oracle和Linux上面当进程收到一个QUIT信号时也会产生分析文件,如果ctrl+\或者ctrl+break按下多次,那么将产生多个分析到一个文件中。
多数情况下输出会包含跟踪的id、线程和对象。各种类型的id将会典型的以一个不同的数字开头。
堆分配分析 heap=sites
下面是一个通过在一个输入文件集上面运行javac生成的堆分配分析文件。这里只展示部分输出:
$javac -J-agentlib:hprof=heap=sites Hello.java
SITES BEGIN (ordered by live bytes) Wed Oct 4 13:13:42 2006
percent live alloc'ed stack class
rank self accum bytes objs bytes objs trace name
1 44.13% 44.13% 1117360 13967 1117360 13967 301926 java.util.zip.ZipEntry
2 8.83% 52.95% 223472 13967 223472 13967 301927 com.sun.tools.javac.util.List
3 5.18% 58.13% 131088 1 131088 1 300996 byte[]
4 5.18% 63.31% 131088 1 131088 1 300995 com.sun.tools.javac.util.Name[]
堆分析信息中一个至关重要的片段是程序不同部分发生的分配的数量。前面的SITES记录显示了总
空间的
44.13%是为java.util.zip.ZipEntry对象分配的。
一个好的关联allocation sites到源码的方法是记录导致堆分配的动态堆栈跟踪。一下输出显示了分析输出的另一部分,它演示了关于这4个allocation sites的堆栈跟踪。
TRACE 301926:
java.util.zip.ZipEntry.<init>(ZipEntry.java:101)
java.util.zip.ZipFile+3.nextElement(ZipFile.java:417)
com.sun.tools.javac.jvm.ClassReader.openArchive(ClassReader.java:1374)
com.sun.tools.javac.jvm.ClassReader.list(ClassReader.java:1631)
TRACE 301927:
com.sun.tools.javac.util.List.<init>(List.java:42)
com.sun.tools.javac.util.List.<init>(List.java:50)
com.sun.tools.javac.util.ListBuffer.append(ListBuffer.java:94)
com.sun.tools.javac.jvm.ClassReader.openArchive(ClassReader.java:1374)
TRACE 300996:
com.sun.tools.javac.util.Name$Table.<init>(Name.java:379)
com.sun.tools.javac.util.Name$Table.<init>(Name.java:481)
com.sun.tools.javac.util.Name$Table.make(Name.java:332)
com.sun.tools.javac.util.Name$Table.instance(Name.java:349)
TRACE 300995:
com.sun.tools.javac.util.Name$Table.<init>(Name.java:378)
com.sun.tools.javac.util.Name$Table.<init>(Name.java:481)
com.sun.tools.javac.util.Name$Table.make(Name.java:332)
com.sun.tools.javac.util.Name$Table.instance(Name.java:349)
堆栈中的每一帧都包含一个类名、一个方法名、一个源文件名和行号。用户可以设置HPROF代理可以收集的最大帧数,默认是4。堆栈不止揭示了哪个方法执行过堆分配,而且也揭露了哪个方法最终对标记导致内存分配的调用负责。
Heap Dump Profile heap=dump
一个堆dump可以通过heap=dump选项获得。堆dump可以是ascii或者二进制格式,取决于格式选项的设置。例如jhat这样的工具使用二进制格式所以format=b选项必须要设置。当指定了二进制格式时,dump包含原始类型实例字段和原始数组内容。
下面的命令从执行javac编译器的堆中产生一个当前存活对象的完整的ascii文本格式的dump:
avac -J-agentlib:hprof=heap=dump Hello.java
输出是一个非常大的文件。它包含GC决定的根节点,和堆中根节点可达的每个java对象的入口。下面是一个简单堆dump的部分:
HEAP DUMP BEGIN (39793 objects, 2628264 bytes) Wed Oct 4 13:54:03 2006
ROOT 50000114 (kind=<thread>, id=200002, trace=300000)
ROOT 50000006 (kind=<JNI global ref>, id=8, trace=300000)
ROOT 50008c6f (kind=<Java stack>, thread=200000, frame=5)
:
CLS 50000006 (name=java.lang.annotation.Annotation, trace=300000)
loader 90000001
OBJ 50000114 (sz=96, trace=300001, class=java.lang.Thread@50000106)
name 50000116
group 50008c6c
contextClassLoader 50008c53
inheritedAccessControlContext 50008c79
blockerLock 50000115
OBJ 50008c6c (sz=48, trace=300000, class=java.lang.ThreadGroup@50000068)
name 50008c7d
threads 50008c7c
groups 50008c7b
ARR 50008c6f (sz=16, trace=300000, nelems=1,
elem type=java.lang.String[]@5000008e)
[0] 500007a5
CLS 5000008e (name=java.lang.String[], trace=300000)
super 50000012
loader 90000001
:
HEAP DUMP END
每个记录都是一个ROOT,OBJ,CLS或者ARR,分别对应root,一个对象实例,一个类,或者一个数组。16进制数字是HPROF分配的标识,这些数字被用来显示一个对象到另一个对象的引用。在前面的例子中java.lang.Thread实例50000114有一个到它的线程组50008c6c和其他对象的引用。
通常情况下,由于输出非常大,有一个工具来可视化或者处理堆dump的输出很有必要。例如jhat。
CPU使用采样分析cpu=samples
HPROF工具能通过采样线程来收集CPU使用率信息。下面的例子显示了如何通过运行javac编译器来生成一个CPU使用率采样分析:
javac -J-agentlib:hprof=cpu=samples Hello.java
CPU SAMPLES BEGIN (total = 462) Wed Oct 4 13:33:07 2006
rank self accum count trace method
1 49.57% 49.57% 229 300187 java.util.zip.ZipFile.getNextEntry
2 6.93% 56.49% 32 300190 java.util.zip.ZipEntry.initFields
3 4.76% 61.26% 22 300122 java.lang.ClassLoader.defineClass2
4 2.81% 64.07% 13 300188 java.util.zip.ZipFile.freeEntry
5 1.95% 66.02% 9 300129 java.util.Vector.addElement
6 1.73% 67.75% 8 300124 java.util.zip.ZipFile.getEntry
7 1.52% 69.26% 7 300125 java.lang.ClassLoader.findBootstrapClass
8 0.87% 70.13% 4 300172 com.sun.tools.javac.main.JavaCompiler.<init>
9 0.65% 70.78% 3 300030 java.util.zip.ZipFile.open
10 0.65% 71.43% 3 300175 com.sun.tools.javac.main.JavaCompiler.<init>
...
CPU SAMPLES END
HPROF代理周期性的采样所有运行中的线程堆栈以记录最活跃的堆栈跟踪。上面的count字段预示了一个堆栈被发现活跃了多少次。这些堆栈跟踪对应到应用中的CPU使用热点。
CPU使用时间分析cpu=times
HPROF工具能通过注入代码到每个方法的入口和出口来收集CPU使用信息,从而跟踪确切方法的调用次数和每个方法花费的时间。这个进程使用字节码索引(BCI)并运行得比cpu=samples要慢。下面的例子显示了部分CPU使用时间分析输出:
$javac -J-agentlib:hprof=cpu=times Hello.java
CPU TIME (ms) BEGIN (total = 2082665289) Wed oct 4 13:43:42 2006
rank self accum count trace method
1 3.70% 3.70% 1 311243 com.sun.tools.javac.Main.compile
2 3.64% 7.34% 1 311242 com.sun.tools.javac.main.Main.compile
3 3.64% 10.97% 1 311241 com.sun.tools.javac.main.Main.compile
4 3.11% 14.08% 1 311173 com.sun.tools.javac.main.JavaCompiler.compile
5 2.54% 16.62% 8 306183 com.sun.tools.javac.jvm.ClassReader.listAll
6 2.53% 19.15% 36 306182 com.sun.tools.javac.jvm.ClassReader.list
7 2.03% 21.18% 1 307195 com.sun.tools.javac.comp.Enter.main
8 2.03% 23.21% 1 307194 com.sun.tools.javac.comp.Enter.complete
9 1.68% 24.90% 1 306392 com.sun.tools.javac.comp.Enter.classEnter
10 1.68% 26.58% 1 306388 com.sun.tools.javac.comp.Enter.classEnter
...
CPU TIME (ms) END
这个输出中,count表示这个方法进入的真实次数,百分比表示这个方法消耗的线程cpu时间。