本コラムの対象:
HPROFを今すぐ使いたい方。プロファイリングの基本を理解している方。
HPROFとはJava 2 SDKに付属しているJVMPIプロファイラエージェントです。 無料で使用でき、CPU/ヒーププロファイリングやモニターオブジェクトの状態監視など、プロファイラが備えるべき基本的な機能を提供しています。
これらの機能を利用することで、Javaプログラムのパフォーマンス分析、メモリ使用量の改善、 デッドロック問題の解決に役立つ情報の収集を行うことができます。
| 今回の内容 |
|
◆ ヒーププロファイリング ◆ CPUプロファイリング ◆ モニターオブジェクトの状態監視 ◆ オプション一覧 |
HPROF、JVMPIについては、JVMPIリファレンスもご覧ください。
JavaVMの実行中に結果を出力したい場合には、doeオプションにnを設定します。
こうしておくとHPROFは、JavaVM終了時にはプロファイリング結果を出力しません。
Windows環境の場合は[Ctrl]+[Break]、Solaris環境の場合は[Ctrl]+[\]と押すことで、
JavaVM実行中に結果を出力することができます。Solaris環境ではコマンドラインから
"kill -3"と入力しても、同じ結果を得ることができます。(※1)
サーバプロセス、デーモンプロセスなどの稼働し続けるJavaプログラムをHPROFで
プロファイリングする場合には、この方法を用いると良いでしょう。
ただしこの操作はJavaコンソール上で行う必要があります。
GUIプログラムの実行時などは注意してください。
JavaVMは、VMのシャットダウン時にプロファイラエージェントに対して
JVMPI_EVENT_JVM_SHUT_DOWNイベントを通知します。
hprofは、JavaVMからのJVMPI_EVENT_JVM_SHUT_DOWNイベントを受信したタイミングで、
各ダンプイベント(JVMPI_EVENT_HEAP_DUMPやJVMPI_EVENT_MONITOR_DUMPなど)の
発行により、JavaVMの状態を取得し、出力します。
実行例:
java -Xrunhprof:heap=sites,file=sites.txt HprofSample1
heapオプションの引数には、「sites」、「dump」、「all」が指定可能で、それぞれ以下のような動作をします。 通常は「sites」の指定で十分でしょう。
| 引数 | 動作 |
| sites | 使用メモリの統計情報を出力する。 |
| dump | ヒープのダンプを出力する。 |
| all | sitesとdumpの両方を出力する。 |
また、プロファイリング結果の出力ファイル名は「file」オプションで指定することができます。
プロファイル結果は適宜省略して記述します。また、文中、赤太文字で記述されている部分は説明のための補足であり、実際のファイルには出力されません。
→ サンプルプログラム
プロファイリング結果(sites指定):
JAVA PROFILE 1.0.1, created Mon Jul 14 08:35:46 2003 Header for -Xhprof ASCII Output Copyright 1998 Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. All Rights Reserved. (略) -------- THREAD START (obj=a3fb78, id = 1, name="Finalizer", group="system") THREAD START (obj=a3f780, id = 2, name="Reference Handler", group="system") THREAD START (obj=9aa4b0, id = 3, name="main", group="main") THREAD START (obj=abe4558, id = 4, name="Signal Dispatcher", group="system") THREAD END (id = 3) THREAD START (obj=abe30a8, id = 5, name="DestroyJavaVM", group="main") THREAD END (id = 5) TRACE 264: ←トレース番号 java.util.HashMap.addEntry(HashMap.java:719) ←スタックトレース java.util.HashMap.put(HashMap.java:388) java.security.SecureClassLoader.getProtectionDomain(SecureClassLoader.java:158) java.security.SecureClassLoader.defineClass(SecureClassLoader.java:123) TRACE 1: <empty> TRACE 60: java.util.zip.ZipFile.getTotal(ZipFile.java:Native method) java.util.zip.ZipFile.<init>(ZipFile.java:114) java.util.jar.JarFile.<init>(JarFile.java:117) java.util.jar.JarFile.<init>(JarFile.java:55) TRACE 8: java.net.URLClassLoader.findClass(URLClassLoader.java:186) java.lang.ClassLoader.loadClass(ClassLoader.java:299) java.lang.ClassLoader.loadClass(ClassLoader.java:292) sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:265) (略) TRACE 276: ObjectHolder.<init>(HprofSample1.java:44) ←生成箇所 HprofSample1.execute(HprofSample1.java:23) HprofSample1.main(HprofSample1.java:11) (略) SITES BEGIN (ordered by live bytes) Mon Jul 14 08:35:47 2003 percent live alloc'ed stack class rank self accum bytes objs bytes objs trace name (1) (2) (3) (4) (5) (6) (7) (8) (9) 1 38.50% 38.50% 786432 32768 786432 32768 276 java.lang.String ← rank=1 2 25.67% 64.17% 524288 32768 524288 32768 277 java.lang.Integer 3 7.99% 72.16% 163136 1135 163560 1145 1 [C 4 7.22% 79.38% 147456 1024 147456 1024 274 java.lang.String 5 7.22% 86.60% 147456 1024 147456 1024 275 java.lang.Integer 6 2.61% 89.20% 53248 257 53248 257 1 [B 7 1.33% 90.53% 27136 342 27136 342 1 [S 8 1.09% 91.63% 22344 931 22584 941 1 java.lang.String 9 0.96% 92.59% 19608 370 19608 370 1 java.lang.Object 10 0.81% 93.39% 16464 294 16464 294 1 java.lang.Class 11 0.80% 94.19% 16384 1024 16384 1024 273 ObjectHolder 12 0.75% 94.94% 15224 259 605432 271 1 [I 13 0.56% 95.50% 11496 479 11496 479 1 java.util.HashMap$Entry 14 0.33% 95.83% 6664 5 6664 5 74 [B 15 0.30% 96.13% 6064 63 6064 63 141 [C 16 0.25% 96.38% 5136 1 10272 7 278 java.lang.Object 17 0.22% 96.60% 4536 63 4536 63 149 [C 18 0.15% 96.75% 3024 13 3024 13 1 java.util.HashMap$Entry 19 0.13% 96.88% 2640 5 2640 5 89 [B 20 0.12% 97.00% 2520 63 2520 63 143 java.util.HashMap (略) SITES END
前半部分がオブジェクトをアロケートした場所を表すトレース出力部分です。
実際にオブジェクトがメモリに展開された場所のスタックトレースとトレース番号を出力しています。
後半部分がSITES出力です。メモリ使用量順にソートされています。
各カラム内容は、次の表に示す通りです。
| 数字 | タイトル | 意味 |
| (1) | rank | 消費メモリの順位 |
| (2) | percent self | 消費メモリのパーセンテージ |
| (3) | percent accum | 消費メモリの累積パーセンテージ(上位rankのpercent selfとの和) |
| (4) | live bytes | ライブオブジェクト(GCされていないオブジェクト)の消費バイト数 |
| (5) | live objs | ライブオブジェクトのオブジェクト数 |
| (6) | alloc'ed bytes | アロケートされたオブジェクト(GCされたオブジェクトを含む)の消費バイト数 |
| (7) | alloc'ed objs | アロケートされたオブジェクトのオブジェクト数 |
| (8) | stack trace | 対応するトレース番号 |
| (9) | class name | クラス名 |
クラス名の部分に記述されている「[C」や「[I」といったクラス名は配列を表します。
| クラス名 | 意味 |
| [Lクラス名 | オブジェクトの配列 |
| [Z | boolean型の配列 |
| [B | byte型の配列 |
| [C | char型の配列 |
| [S | short型の配列 |
| [I | int型の配列 |
| [J | long型の配列 |
| [F | float型の配列 |
| [D | double型の配列 |
SITES出力から、最もメモリを消費しているオブジェクトを生成している部分を特定してみましょう。
- オブジェクトの特定
- トレース番号の特定
- 生成箇所の特定
SITESの rank = 1 の部分を見てみると、最もメモリを消費しているオブジェクトは全体の38.50%を占めているStringオブジェクトであることが分かります。
rank = 1 の stack trace 項目を見ると、対応するトレース番号が 276 であることが分かります。
トレース番号 276 の出力部分を見ると、このオブジェクトを生成している箇所はプログラムの44行目、ObjectHolderクラスのコンストラクタであることが分かります。
実行例:
java -Xrunhprof:cpu=times,file=cpu_times.txt HprofSample2
cpuオプションの引数には「samples」、「times」、「old」が指定可能で、それぞれ以下のような動作をします。
| 引数 | 動作 |
| samples | 実行中の全てのスレッドを定期的にサンプリングし、各サンプリングタイミングにおいてアクティブになったスレッド(メソッド)をカウントする。 |
| times | メソッドのIN/OUTを監視し、メソッドのコール回数をカウントする。 |
| old | timesと同様。出力形式が、旧hprofフォーマットとなる。 |
「samples」と「times」の結果はほぼ同じになります。ただしカウンタの値が異なります(samplesはサンプリング回数、timesはメソッドコール回数)。「old」を指定することはまずないでしょう。
→ サンプルプログラム
プロファイリング結果(samples指定):
JAVA PROFILE 1.0.1, created Mon Jul 14 08:35:49 2003 Header for -Xhprof ASCII Output Copyright 1998 Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. All Rights Reserved. (略) -------- THREAD START (obj=ab81ef8, id = 1, name="Finalizer", group="system") THREAD START (obj=ab82040, id = 2, name="Reference Handler", group="system") THREAD START (obj=ab82120, id = 3, name="main", group="main") THREAD START (obj=ab85758, id = 4, name="HPROF CPU profiler", group="system") THREAD START (obj=ab85860, id = 5, name="Signal Dispatcher", group="system") THREAD END (id = 3) THREAD START (obj=ab82168, id = 6, name="DestroyJavaVM", group="main") THREAD END (id = 6) TRACE 4: <empty> TRACE 5: HprofSample2.shortMethod(HprofSample2.java:35) ←最ヒット箇所 HprofSample2.execute(HprofSample2.java:21) ←shortMethod 呼び出し箇所 HprofSample2.main(HprofSample2.java:9) (略) CPU SAMPLES BEGIN (total = 20) Mon Jul 14 08:35:52 2003 (1) (2) (3) (4) (5) (6) rank self accum count trace method 1 50.00% 50.00% 10 5 HprofSample2.shortMethod ← rank=1 2 35.00% 85.00% 7 6 HprofSample2.longMethod 3 5.00% 90.00% 1 1 sun.misc.URLClassPath$JarLoader.parseExtensionsDependencies 4 5.00% 95.00% 1 3 java.security.SecureClassLoader.getPermissions 5 5.00% 100.00% 1 2 java.util.jar.Attributes.read CPU SAMPLES END
プロファイリング結果(times指定):
JAVA PROFILE 1.0.1, created Mon Jul 14 08:35:54 2003 Header for -Xhprof ASCII Output Copyright 1998 Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. All Rights Reserved. (略) -------- THREAD START (obj=ab84818, id = 2, name="Finalizer", group="system") THREAD START (obj=ab84930, id = 1, name="Reference Handler", group="system") THREAD START (obj=ab849e8, id = 3, name="main", group="main") THREAD START (obj=ab87750, id = 4, name="Signal Dispatcher", group="system") THREAD END (id = 3) THREAD START (obj=ab854e0, id = 5, name="DestroyJavaVM", group="main") THREAD END (id = 5) TRACE 1: <empty> TRACE 3: HprofSample2.longMethod(HprofSample2.java:Unknown line) HprofSample2.execute(HprofSample2.java:Unknown line) HprofSample2.main(HprofSample2.java:Unknown line) (略) CPU TIME (ms) BEGIN (total = 2652) Mon Jul 14 08:35:57 2003 (1) (2) (3) (4) (5) (6) rank self accum count trace method 1 49.81% 49.81% 65535 5 HprofSample2.shortMethod 2 46.04% 95.85% 1 3 HprofSample2.longMethod 3 2.26% 98.11% 1 2 HprofSample2.execute 4 0.38% 98.49% 63 8 java.lang.String.<init> 5 0.38% 98.87% 5 4 java.io.WinNTFileSystem.getLastModifiedTime 6 0.38% 99.25% 1036 9 java.lang.Character.toLowerCase 7 0.38% 99.62% 68 7 java.util.AbstractMap.<init> 8 0.38% 100.00% 1 6 sun.net.www.URLConnection.<init> CPU TIME (ms) END注:
このプログラムをtimes指定で実行するとHPROFが"HPROF ERROR : stack underflow"というメッセージを出力してstack underflowを起こすため、トレース出力の行番号は"Unknown line"と出力されています。
サンプリング回数の出力部分以外は、samples指定、times指定共に同じ内容を示しています。
前半部分がトレース出力です。内容はヒーププロファイリングの項で説明したとおりです。
後半部分が使用したCPU時間になります。この部分は、使用したCPU時間順にソートされています。
1行目のtotal=xxxがサンプリング回数です。各カラム内容は、次の表に示す通りです。
| 数字 | タイトル | 意味 |
| (1) | rank | CPU時間の順位 |
| (2) | self | CPU時間のパーセンテージ |
| (3) | accum | CPU使用時間の累積パーセンテージ(上位rankのselfとの和) |
| (4) | count | ヒットしたサンプリングタイミング数(samples指定)、またはメソッドのコール回数(times指定) |
| (5) | trace | 対応するトレース番号 |
| (6) | method | メソッド名 |
ボトルネックとなるメソッドは、ヒーププロファイリングの場合と同様に、各統計情報に対応するトレース番号をたどることで発見することができます。
ここで注意しなければならないのは、プロファイリング結果は「そのメソッド自体が占有したCPU時間」をあらわしていることです。
例えばtimes指定の出力を見ると、HprofSample2.shortMethod()の実行が全体の約50%を占めていますが、shortMethod()の呼び出し元であるHprofSample2.execute()メソッドのCPU占有率は3%未満です。 特定のメソッドから始まる処理全体がCPUを占有した割合を知るには、注目したいメソッドから始まる一連のメソッドについてCPU占有率を足していく必要があります。
samples出力から最もCPUを利用しているメソッドを特定してみましょう。
- オブジェクトの特定
- トレース番号の特定
- 呼び出し箇所の特定
SITESの rank = 1 の部分を見てみると、最もCPU時間を使用しているオブジェクトは全体の50.00%を占めている HprofSample2.shortMethod メソッドであることが分かります。
rank = 1 の stack trace 項目を見ると、対応するトレース番号が 5 であることが分かります。
トレース番号 5 の出力部分を見ると、HprofSample2.shortMethod メソッドは HprofSample2.execute メソッドから呼び出されていることが分かります。
一般的に、マルチスレッドプログラムが同期処理を行う場合、特定のロック用オブジェクトを取り合うことで実現します。このロックオブジェクトにあたるものがモニターオブジェクトです。
簡単に言うと、synchronizedキーワードによって同期指定されたコードを持つオブジェクトがモニターオブジェクトとなります。
モニターオブジェクトの状態を出力するには、「monitor」オプションを使用します。
ただしデッドロックした場合に、どちらのオブジェクトが先に相手を掴んだのかまでは分かりません。
実行例:
java -Xrunhprof:monitor=y,file=monitor.txt HprofSample3
→ サンプルプログラム
プロファイリング結果(monitor指定):
JAVA PROFILE 1.0.1, created Mon Jul 14 08:35:59 2003
Header for -Xhprof ASCII Output
Copyright 1998 Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto,
California, 94303, U.S.A. All Rights Reserved.
(略)
--------
THREAD START (obj=ab85eb8, id = 1, name="Finalizer", group="system")
THREAD START (obj=ab86060, id = 2, name="Reference Handler", group="system")
THREAD START (obj=ab861a0, id = 3, name="main", group="main")
THREAD START (obj=ab88cb8, id = 4, name="Signal Dispatcher", group="system")
THREAD START (obj=abf8e08, id = 5, name="Thread-1", group="main")
THREAD START (obj=abf8de0, id = 6, name="Thread-2", group="main")
THREAD START (obj=abf9e48, id = 7, name="Thread-0", group="main")
THREAD END (id = 7)
THREAD END (id = 3)
TRACE 1:
<empty>
TRACE 2:
HprofSample3.main(HprofSample3.java:9)
THREAD START (obj=ab861a0, id = 9, name="main", group="main")
MONITOR DUMP BEGIN
MONITOR SyncClass(abff2a8)
owner: thread 5, entry count: 1
waiting to enter: thread 6 ←thread 6 が待っている
MONITOR java.lang.ref.ReferenceQueue$Lock(9a9ff0) unowned
waiting to be notified: thread 1
MONITOR SyncClass(abff2c8)
owner: thread 6, entry count: 1
waiting to enter: thread 5 ←thread 5 が待っている
MONITOR java.lang.ref.Reference$Lock(9a9f30) unowned
waiting to be notified: thread 2
MONITOR java.lang.Class(a500d8)
owner: thread 9, entry count: 0
MONITOR DUMP END
MONITOR TIME BEGIN (total = 0 ms) Mon Jul 14 08:36:01 2003
MONITOR TIME END
前半のトレース出力部分はこれまでと同様です。
後半部分がJavaVMシャットダウン時のモニターオブジェクトの状態です。この出力結果から、デッドロックが発生していないか、分析してみましょう。
- 解放待ちスレッドの特定
- モニターオブジェクト所有スレッドの特定
- デッドロックの有無を確認
「waiting to enter: thread XX」と記述されている箇所を確認します。ここがこのモニターオブジェクトの解放を待っているスレッドを示しています。
このモニターオブジェクトを保持しているスレッドを特定するため、1でチェックしたモニターオブジェクトの「owner: thread XX」の記述を確認します。
ここで、互いにモニターオブジェクトの解放を待っている部分があるかどうか、確認してみます。 すると、SyncClassオブジェクトを所有するスレッド、thread 5、thread 6が存在し、互いのスレッドの解放を待っている状態であることがわかります。
| オプション | 機能 | デフォルト値 |
| heap=dump|sites|all | ヒープのプロファイリングを行う。 | all |
| cpu=samples|times|old | CPU 時間のプロファイリングを行う。 | off |
| monitor=y|n | モニターの競合のプロファイリングを行う。 | n |
| format=a|b | 出力形式。aはASCII形式、bはバイナリ形式。 | a |
| file=name | データを出力するファイル名を指定する。 | java.hprof (ASCII の場合は .txt) |
| net=host:port | 指定した場合、プロファイリングデータをソケット経由で送信する。 | ファイル出力 |
| depth=size | 出力スタックトレースの深さ。 | 4 |
| cutoff=value | 出力のカットオフポイント。例えば0.2と指定した場合、ヒープ占有率またはCPU占有率が20%未満のデータは出力されない。 | 0.0001 |
| lineno=y|n | トレースに行番号を出力するか。 | y |
| thread=y|n | トレースにスレッド番号を出力するか。 | n |
| doe=y|n | 終了時にダンプするかを指定する。 | y |
また、フリーで提供されている、HPROF出力結果解析ツールなどと併用することで威力を発揮します。
ENdoSnipeを利用することでHPROFとは異なる側面からJavaの内部動作をみることができます。
詳細な情報はこちらをクリック。
| ※1 | 『プロフェッショナルJava 下 JDBC、XML、分散オブジェクト、セキュリティ、チューニング編』 Brett Spell著 株式会社インプレス発行 2001年 |








Copyright (C) 2008 SMG Co., ltd. All Rights Reserved.