Native Memory Tracking とは
NMT(Native Memory Tracking) は Hotspot VM の機能。
JVM内部で実行されるネイティブメモリ割り当てを追跡できる(ただし JNIコードなどのJVM外部で割り当てられたネイティブメモリは追跡できない)。
NMT を利用するには JVM オプションを指定する(デフォルトは off)。
-XX:NativeMemoryTracking=off|summary|detail
summary で確認し、より詳細が必要な場合に detail を指定すると良い。
detail を指定するとメソッド単位でメモリ割り当てを確認できる。
有効化すると JVM は、malloc/free および mmap/munmap の呼び出しにフックして、メモリ使用量を計算する。
JVMのパフォーマンスは 5-10% 低下するとされている。
Native Memory の確認
NMT を有効化したら、jps で PID を確認し、jcmd でメモリの割り当て状況を確認できる。
jps -l jcmd <pid> VM.native_memory scale=MB
以下のような出力が得られる(scale=MB を指定しない場合はバイト単位の出力となる)。
jcmd <pid> VM.native_memory scale=MB <pid>: Native Memory Tracking: (Omitting categories weighting less than 1MB) Total: reserved=5400MB, committed=73MB malloc: 11MB #84202, peak=54MB #52102 mmap: reserved=5389MB, committed=62MB - Java Heap (reserved=4008MB, committed=18MB) (mmap: reserved=4008MB, committed=18MB, at peak) - Class (reserved=1024MB, committed=4MB) (classes #4874) ( instance classes #4415, array classes #459) (mmap: reserved=1024MB, committed=4MB, at peak) ( Metadata: ) ( reserved=64MB, committed=13MB) ( used=13MB) ( waste=0MB =1.12%) ( Class space:) ( reserved=1024MB, committed=4MB) ( used=4MB) ( waste=0MB =1.99%) - Thread (reserved=22MB, committed=1MB) (threads #22) (stack: reserved=22MB, committed=1MB, peak=1MB) - Code (reserved=245MB, committed=13MB) (malloc=3MB tag=Code #17420) (at peak) (mmap: reserved=242MB, committed=10MB, at peak) - GC (reserved=13MB, committed=0MB) (mmap: reserved=13MB, committed=0MB, at peak) - Compiler (reserved=0MB, committed=0MB) (arena=0MB #6) (peak=44MB #15) - Internal (reserved=2MB, committed=2MB) (malloc=1MB tag=Internal #8798) (at peak) - Symbol (reserved=3MB, committed=3MB) (malloc=2MB tag=Symbol #38061) (at peak) - Native Memory Tracking (reserved=1MB, committed=1MB) (tracking overhead=1MB) - Shared class space (reserved=16MB, committed=15MB, readonly=0MB) (mmap: reserved=16MB, committed=15MB, peak=15MB) - Arena Chunk (reserved=0MB, committed=0MB) (malloc=0MB tag=Arena Chunk #57) (peak=49MB #1006) - Metaspace (reserved=64MB, committed=13MB) (mmap: reserved=64MB, committed=13MB, at peak)
それぞれの用途で、どれだけのメモリが利用されているかが committed の値から分かる。
#付の数字はカウントを示す(例えば classes #4885 は4,885クラスが読み込まれたことを示す)。
以下の出力は Metaspace 内の Class space が 4,874 個のクラスが読みで 4MB を消費していることがわかる。
Class (reserved=1024MB, committed=4MB)
(classes #4874)
( instance classes #4415, array classes #459)
(mmap: reserved=1024MB, committed=4MB, at peak)
JDK 21~22でNMTの出力が改良されている。NMT: Display peak values, NMT: Make peak values available in release builds。
その時点の値に加え、peak としてピーク値を合わせて出力するようになった(カウント値はピークサイズに達した時点のカウント)。
表示内容に関する詳細なドキュメントは無いので、以下を見るのが早い。
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/nmt/memReporter.cpp
NMT カテゴリ
カテゴリには以下がある。
| カテゴリ | 説明 |
|---|---|
| Java Heap | Javaヒープ |
| Class | クラスメタデータ(圧縮クラスポインタ Compressed Class Pointers が参照する Klass 構造体) |
| Thread | スレッド・データ構造、リソース領域、ハンドル領域などを含む、スレッドによって使用されるメモリ |
| Code | バイトコードをアセンブリ命令にコンパイルする際のコードキャッシュ |
| GC | カードテーブルなどのGCによるデータ使用(記憶集合(remembered sets)を除く) |
| GCCardSet | GCの記憶集合(remembered sets)により使用されるデータ(G1GCのみ) |
| Compiler | コードを生成するときにコンパイラによって使用されるメモリー・トラッキング |
| Internal | コマンドライン・パーサー、JVMTI、プロパティおよびその他によって使用されるメモリーなど、前述のカテゴリに該当しないメモリ |
| Other | カテゴリで分類できないもの |
| Symbol | シンボルで利用されるメモリ(String Table / Runtime Constant Pool) |
| Native Memory Tracking | NMTによって使用されるメモリ |
| Shared class space | Class Data Sharing のメモリマッピング |
| Arena Chunk | アリーナのチャンク・プール内のチャンクによって使用されるメモリ |
| Metaspace | クラスメタデータ(Classカテゴリ内の Metadata と同) |
| Logging | ロギングによって使用されるメモリ |
| Arguments | 引数用のメモリ |
| Module | モジュールによって使用されるメモリ |
MetaspaceSize = Metaspaceカテゴリのメモリ使用量(=Class カテゴリ内のMetadata内訳) + Class カテゴリのメモリ使用量。
カテゴリ別チューニングオプション
NMT カテゴリに関連する JVM オプションには以下がある。
| カテゴリ | 説明 |
|---|---|
| Java Heap | -Xms<size> -Xmx<size> |
| Class | -XX:CompressedClassSpaceSize=<size> |
| Thread | -Xss<size> –XX:ThreadStackSize=<size> |
| Code | -XX:InitialCodeCacheSize=<size> -XX:ReservedCodeCacheSize=<size> |
| GC | -XX:+UseSerialGC -XX:+UserG1GC |
| Symbol | -XX:StringTableSize=<N> |
| Shared class space | -Xshare:off |
| Metaspace | -XX:MetaspaceSize=<size> -XX:MaxMetaspaceSize=<size> |
MetaspaceSize は、旧来のPermGen。 MetaspaceSize の中身は、Class Space とそれ以外(Metadata)の領域に分かれており、Class Space のサイズを規定するものが CompressedClassSpace となる。
ベースラインからの差分
ある時点の値をベースラインとして、ベースラインとの差分を表示することができる。
jcmd <pid> VM.native_memory baseline scale=MB jcmd <pid> VM.native_memory detail.diff jcmd <pid> VM.native_memory summary.diff scale=MB
メモリリークの調査は、このコマンドを使うことになる。
+ - でベースラインとの差分が合わせて表示される。
jcmd 18628 VM.native_memory summary.diff 18628: Native Memory Tracking: (Omitting categories weighting less than 1KB) Total: reserved=5517979KB -97KB, committed=87535KB -97KB malloc=15887KB -97KB #107308 +12 ...
NMT jcmd オプション
jcmdを使用して収集したデータをダンプし、必要に応じて、そのデータを最後のベースラインと比較する。
jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
オプションの説明は以下。
| オプション | 説明 |
|---|---|
| summary | カテゴリ別に集約されたサマリーを出力する |
| detail | カテゴリ別に集約されたメモリー使用量、仮想メモリー・マップ、コール・サイト別に集約されたメモリー使用量を出力する |
| baseline | 比較のために新しいメモリー使用量スナップショットを作成する |
| summary.diff | 最後のベースラインに照らして新しいサマリー・レポートを出力する |
| detail.diff | 最後のベースラインに照らして新しい詳細レポートを出力する |
| shutdown | NMTを停止する |