JavaにおけるHeapのClassHistogramを取得する複数の方法とその違い
TL;DR
方法 | Full GCの有無 | タイミング | 条件 |
---|---|---|---|
JFR の jdk.ObjectCount | あり | 定期的 | JFRが使える |
-XX:+PrintClassHistogram | あり | シグナル受信時 | JDK 1.4.2~ |
jmap -histo:live | あり | 実行時 | JDK 1.4.2~ |
jcmd GC.class_histogram | あり | 実行時 | JDK 7~ |
jmap -histo | なし | 実行時 | JDK 1.4.2~ |
jcmd GC.class_histogram -all | なし | 実行時 | JDK 7~ |
-XX:+PrintClassHistogramBeforeFullGC | なし | Full GC 前 | JDK 6(?)~8 |
-XX:+PrintClassHistogramAfterFullGC | なし | Full GC 後 | JDK 6(?)~8 |
-Xlog:classhisto*=trace | なし | Full GC 前後 | JDK 9~ |
はじめに
JavaにおけるHeapのClassHistogramを取得する方法が複数種類あり、それぞれの違いがわからなかったので調べた。
実装の詳細については f3ca0cab75f2faf9ec88f7a380490c9589a27102
時点の openjdk/jdk の情報を元にしている。
過去のバージョン時点でも同様の挙動であるとは限らない。
OpenJDK から得た情報なので 6 より前の情報は正確でない可能性がある。
-XX:+PrintClassHistogram を使う
Java 1.4.2 からあるオプションで CONTROL + BREAK を外部から送ったタイミングでログに出力される。 生存しているオブジェクトのみを出力するために Full GC をトリガーする。
Signal Dispatcher スレッド (signal_thread_entry) -> VMThread::execute(VM_GC_HeapInspection)
jmap -histo を使う
Java 1.4.2 からあるツールで VM にアタッチして heapinspect を実行する。
Attach Listener スレッド(attach_listener_thread_entry) -> heap_inspection -> VMThread::execute(VM_GC_HeapInspection) という流れで処理される。
VM_GC_HeapInspection が実体。 live というオプションを付けると Full GC をトリガーする。
jcmd GC.class_histogram を使う
Java 7 からあるツールで VM にアタッチして jcmd を実行する。
Attach Listener スレッド(attach_listener_thread_entry) -> jcmd -> DCmd::parse_and_execute -> ClassHistogramDCmd::execute -> VMThread::execute(VM_GC_HeapInspection) という流れで処理される。
-all をつけると Full GC をトリガーしない。
-XX:+PrintClassHistogramBeforeFullGC -XX:+PrintClassHistogramAfterFullGC を使う
Full GC が発生する/したタイミングで出力される。 STW の時間が増加するが自動で出力される。
ClassHistogram は Full GC の前後で見たいことが多いので一番良さそう。
-Xlog:classhisto*=trace
-XX:+PrintClassHistogramBeforeFullGC
と -XX:+PrintClassHistogramAfterFullGC
は Java 9 からの JEP 271 で削除され -Xlog:classhisto*=trace
に変更された。
基本的に同じもの。
どちらか片方のみを有効にすることができなくなった。
JFR の jdk.ObjectCount を 使う
これを JFR で有効にすると VM_GC_SendObjectCountEvent(VM_GC_HeapInspection) によって毎回 Full GC がトリガーされる。 jfr configure で gc の項目を all にすると jdk.ObjectCount が有効な jfc が生成される。
VM_GC_HeapInspection
VM_GC_HeapInspection は safepoint で評価されるので STW が Full GC の有無に関わらず発生する。
まとめ
方法 | Full GCの有無 | タイミング | 条件 |
---|---|---|---|
JFR の jdk.ObjectCount | あり | 定期的 | JFRが使える |
-XX:+PrintClassHistogram | あり | シグナル受信時 | JDK 1.4.2~ |
jmap -histo:live | あり | 実行時 | JDK 1.4.2~ |
jcmd GC.class_histogram | あり | 実行時 | JDK 7~ |
jmap -histo | なし | 実行時 | JDK 1.4.2~ |
jcmd GC.class_histogram -all | なし | 実行時 | JDK 7~ |
-XX:+PrintClassHistogramBeforeFullGC | なし | Full GC 前 | JDK 6(?)~8 |
-XX:+PrintClassHistogramAfterFullGC | なし | Full GC 後 | JDK 6(?)~8 |
-Xlog:classhisto*=trace | なし | Full GC 前後 | JDK 9~ |
余談1
// inspectHeap is not the same as jcmd GC.class_histogram
というコメントがあるが jmap -histo
で実行する inspectheap は jcmd GC.class_histogram
と同様の処理をする。
jmap -histo
と等価なのは jcmd GC.class_histogram -all
だという主張なら理解できる。
余談2
https://docs.oracle.com/en/java/javase/19/docs/specs/man/java.html
-XX:+PrintClassHistogram Setting this option is equivalent to running the jmap -histo command, or the jcmd pid GC.class_histogram command, where pid is the current Java process identifier.
と書いてあるが -XX:+PrintClassHistgoram
と厳密に等価なのは jmap -histo:live
と jcmd GC.class_histogram
だと思われる。
jmap だけデフォルトが Full GC をトリガーしないところため注意が必要だと思われる。
参考リンク
- https://github.com/openjdk/jdk/tree/f3ca0cab75f2faf9ec88f7a380490c9589a27102
- https://wenfeng-gao.github.io/post/java-attach-mechanism
- https://www.slideshare.net/YaSuenag/serviceability-tools
- https://openjdk.org/jeps/271
- https://openjdk.org/jeps/328
- https://blanco.io/blog/jvm-safepoint-pauses/
- https://krzysztofslusarski.github.io/2020/11/13/stw.html
- https://openjdk.org/groups/hotspot/docs/HotSpotGlossary.html#safepoint
- https://hsmemo.github.io/articles/no2935qaz.html
- https://docs.oracle.com/en/java/javase/19/docs/specs/man/java.html