3 メモリー・リークのトラブルシューティング
アプリケーションの実行時間が長くなった場合や、オペレーティング・システムの実行速度が低下したように思われる場合、これはメモリー・リークの兆候である可能性があります。つまり、仮想メモリーが割り当てられているけれども、それが不要になったときに返されません。最終的には、アプリケーションまたはシステムがメモリーを使い果たし、アプリケーションが異常終了します。
この章の構成は、次のとおりです。
JDK Mission Controlを使用したメモリー・リークのデバッグ
フライト・レコーダは、JavaランタイムおよびJavaランタイムで実行されているJavaアプリケーションの詳細情報を記録します。
次の項では、JMCでフライト記録を分析してメモリー・リークをデバッグする方法について説明します。
メモリー・リークの検出
JMCを使用してメモリー・リークを早期に検出し、OutOfmemoryErrors
を防止できます。
スロー・メモリー・リークの検出は難しい場合があります。一般的な症状は、頻繁なガベージ・コレクションにより実行に時間がかかり、その後、アプリケーションが低速になります。やがてOutOfmemoryErrors
が表示される可能性があります。ただし、Javaフライト記録を分析することで、このような問題が発生する前であっても、メモリー・リークを早期に検出できます。
アプリケーションのライブ・セットの経時的な増加がないか確認します。ライブ・セットは、Oldコレクションの後(すべての非ライブ・オブジェクトがガベージ・コレクトされた後)に使用されるJavaヒープ容量です。ライブ・セットを検査するには、JMCを開き、Java管理コンソール(JMX)を使用してJVMに接続します。「MBeanブラウザ」タブを開き、com.sun.management
の下のGarbageCollectorAggregator
MBeanを探します。
JMCを開き、1時間分の一定時間の記録(プロファイリング記録)を開始します。フライト記録を開始する前に、「メモリー・リーク検出」設定から「オブジェクト・タイプ+割当てスタック・トレース+ GCルートへのパス」オプションが選択されていることを確認してください。
記録が完了すると、JMCで記録ファイル(.jfr
)が開きます。「自動分析結果」ページを参照してください。メモリー・リークを検出するには、ページの「ライブ・オブジェクト」セクションにフォーカスを置きます。次に、ヒープ・サイズの問題を示す記録のサンプル図を示します:
「ヒープ・ライブ・セットの傾向」セクションで、ヒープのライブ・セットが急激に増加し、参照ツリーの分析でリーク候補が検出されたことがわかります。
さらに分析するには、「Javaアプリケーション」ページを開き、「メモリー」ページをクリックします。次に、メモリー・リークの問題を示す記録のサンプル図を示します。
グラフから、メモリー使用量が徐々に増加し、メモリー・リークの問題が示されていることを確認できます。
リーク・クラスの調査
Javaフライト記録を使用して、リーク・クラスを識別できます。
リーク・クラスを検索するには、「メモリー」ページを開き、「ライブ・オブジェクト」ページをクリックします。次に、リーク・クラスを示す記録のサンプル図を示します。
追跡されているライブ・オブジェクトのほとんどが実際にはLeak$DemoThread
によって保持されており、このオブジェクトはリークされたchar[]
クラスを保持していることがわかります。詳細な分析は、存続したオブジェクトのサンプリングを含む「結果」タブの古いオブジェクト・サンプル
・イベントを参照してください。このイベントには、割当て時間、割当てスタック・トレース、GCルートに戻るパスが含まれます。
リークの可能性があるクラスが特定されたら、「JVM内部」ページの「TLAB割当て」ページで、オブジェクトが割り当てられた場所のサンプルを調べます。次に、TLAB割当てを示す記録のサンプル図を示します。
割り当てられているクラス・サンプルをチェックします。スロー・リークの場合は、このオブジェクトの割当てが少なく、サンプルがないこともあります。また、特定の割当てサイトのみがリークの原因となっている可能性もあります。コードに必要な変更を加えて、リーク・クラスを修正できます。
jfrツール
Javaフライト・レコーダ(JFR)は、JavaランタイムおよびJavaランタイムで実行されているJavaアプリケーションの詳細情報を記録します。この情報はメモリー・リークを特定するために使用できます。
メモリー・リークを検出するには、JFRがリークの発生時点で実行されている必要があります。JFRのオーバーヘッドは、1%未満と非常に低く、常に本番環境で安全に稼働するように設計されています。
次の例に示すように、アプリケーションがjava
コマンドを使用して起動されたときに記録を開始します。
$ java -XX:StartFlightRecording
JVMがメモリー不足になってjava.lang.OutOfMemoryError
エラーにより終了した場合、接頭辞hs_oom_pid
を持つ記録は多くの場合、JVMが起動されたディレクトリに書き込まれますが、常にそうではありません。記録を取得する別の方法は、次の例に示すように、jcmd
ツールを使用してアプリケーションでメモリーが不足する前に記録をダンプすることです。
$ jcmd pid JFR.dump filename=recording.jfr path-to-gc-roots=true
記録がある場合は、java-home/bin
ディレクトリにあるjfr
ツールを使用して、メモリー・リークの可能性に関する情報を含む古いオブジェクト・サンプル・イベントを出力します。次の例は、pid 16276のアプリケーションに対するコマンドと記録からの出力例を示しています。
jfr print --events OldObjectSample pid16276.jfr
...
jdk.OldObjectSample {
startTime = 18:32:52.192
duration = 5.317 s
allocationTime = 18:31:38.213
objectAge = 74.0 s
lastKnownHeapUsage = 63.9 MB
object = [
java.util.HashMap$Node
[15052855] : java.util.HashMap$Node[33554432]
table : java.util.HashMap Size: 15000000
map : java.util.HashSet
users : java.lang.Class Class Name: Application
]
arrayElements = N/A
root = {
description = "Thread Name: main"
system = "Threads"
type = "Stack Variable"
}
eventThread = "main" (javaThreadId = 1)
}
...
jdk.OldObjectSample {
startTime = 18:32:52.192
duration = 5.317 s
allocationTime = 18:31:38.266
objectAge = 74.0 s
lastKnownHeapUsage = 84.4 MB
object = [
java.util.HashMap$Node
[8776975] : java.util.HashMap$Node[33554432]
table : java.util.HashMap Size: 15000000
map : java.util.HashSet
users : java.lang.Class Class Name: Application
]
arrayElements = N/A
root = {
description = "Thread Name: main"
system = "Threads"
type = "Stack Variable"
}
eventThread = "main" (javaThreadId = 1)
}
...
jdk.OldObjectSample {
startTime = 18:32:52.192
duration = 5.317 s
allocationTime = 18:31:38.540
objectAge = 73.7 s
lastKnownHeapUsage = 121.7 MB
object = [
java.util.HashMap$Node
[393162] : java.util.HashMap$Node[33554432]
table : java.util.HashMap Size: 15000000
map : java.util.HashSet
users : java.lang.Class Class Name: Application
]
arrayElements = N/A
root = {
description = "Thread Name: main"
system = "Threads"
type = "Stack Variable"
}
eventThread = "main" (javaThreadId = 1)
}
...
メモリー・リークの可能性を特定するには、記録内の次の要素を確認します。
-
最初に、古いオブジェクト・サンプル・イベントの
lastKnownHeapUsage
要素が、一定期間にわたり(例では、最初のイベントの63.9 MBから最後のイベントの121.7 MBに)増加していることに注意してください。この増加は、メモリー・リークが存在することを示しています。ほとんどのアプリケーションは、起動時にオブジェクトを割り当て、その後定期的にガベージ・コレクションされる一時オブジェクトを割り当てます。何らかの理由でガベージ・コレクションされないオブジェクトは、時間とともに蓄積され、lastKnownHeapUsage
の値が増加します。 -
次に、
allocationTime
要素を参照して、オブジェクトがいつ割り当てられたかを確認します。通常、起動時に割り当てられるオブジェクトはメモリー・リークではなく、ダンプの取得時の近くで割り当てられるオブジェクトでもありません。objectAge
要素は、オブジェクトが存続している期間を示します。startTime
要素とduration
要素は、メモリー・リークが発生した時点ではなく、OldObject
イベントが発行された時点と、そのデータの収集にかかった時間に関連しています。この情報は無視してかまいません。 -
次に、
object
要素を調べてメモリー・リーク候補を確認します。この例では、java.util.HashMap$Node型のオブジェクトです。これは、java.util.HashMapクラスのtable
フィールドによって保持されます。このフィールドは、Applicationクラスのusers
フィールドによって保持されるjava.util.HashSetによって保持されます。 -
root
要素には、GCルートに関する情報が含まれます。この例では、Applicationクラスは、メイン・スレッドのスタック変数によって保持されています。eventThread
要素は、オブジェクトを割り当てたスレッドに関する情報を提供します。
-XX:StartFlightRecording:settings=profile
オプションを指定してアプリケーションが起動された場合、記録には、次の例に示すようにオブジェクトが割り当てられた場所からのスタック・トレースも含まれます。
stackTrace = [
java.util.HashMap.newNode(int, Object, Object, HashMap$Node) line: 1885
java.util.HashMap.putVal(int, Object, Object, boolean, boolean) line: 631
java.util.HashMap.put(Object, Object) line: 612
java.util.HashSet.add(Object) line: 220
Application.storeUser(String, String) line: 53
Application.validate(String, String) line: 48
Application.login(String, String) line: 44
Application.main(String[]) line: 30
]
この例では、storeUser(String, String)
メソッドが呼び出されたときに、オブジェクトがHashSet
に格納されていたことがわかります。これにより、メモリー・リークの原因は、ユーザーがログアウトしたときにHashSet
から削除されなかったオブジェクトである可能性があると考えられます。
一定の割当て集中型アプリケーションでのオーバーヘッドのため、常に-XX:StartFlightRecording:settings=profile
オプションを指定してすべてのアプリケーションを実行することはお薦めできませんが、通常、デバッグ時であれば問題ありません。通常、オーバーヘッドは2%未満です。
path-to-gc-roots=true
を設定すると、完全ガベージ・コレクションと同様にオーバーヘッドが発生しますが、GCルートへの参照チェーンも提供されます。これは通常、メモリー・リークの原因を確認するために十分な情報です。
OutOfMemoryError例外の理解
java.lang.OutOfMemoryError
のエラーは、Javaヒープにオブジェクトを割り当てるための十分な空間がないときにスローされます。
メモリー・リークの一般的な兆候の1つは、java.lang.OutOfMemoryError
例外です。この場合、ガベージ・コレクタによって新しいオブジェクトを格納するための空間を確保することも、ヒープをこれ以上拡張することもできません。また、このエラーは、Javaクラスのロードをサポートするための十分なネイティブ・メモリーがないときにスローされる場合もあります。ガベージ・コレクションの実行に過剰な時間が消費され、メモリーがほとんど解放されていない場合、まれにjava.lang.OutOfMemoryError
がスローされることがあります。
java.lang.OutOfMemoryError
例外がスローされるときに、スタック・トレースも出力されます。
java.lang.OutOfMemoryError
例外は、ネイティブ割当てを満たすことができないとき(たとえば、スワップ空間が少ない場合)に、ネイティブ・ライブラリ・コードによってスローされることもあります。
OutOfMemoryError
例外を診断する最初のステップは、その例外の原因を突き止めることです。スローされた理由が、Javaヒープがいっぱいであるからなのか、それともネイティブ・ヒープがいっぱいであるからなのかを調べます。原因を見つけることができるように、例外テキストの最後に詳細なメッセージが含まれています(次の例外を参照):
- Exception in thread thread_name: java.lang.OutOfMemoryError: Java heap space
-
原因: Java heap spaceという詳細メッセージは、Javaヒープ内でオブジェクトを割り当てることができなかったことを示しています。このエラーは、必ずしもメモリー・リークを意味しません。この問題は、指定されたヒープ・サイズ(指定されていない場合はデフォルト・サイズ)がアプリケーションにとって十分でないという、単純な構成の問題である可能性があります。
それ以外の場合、特に長期にわたって稼動するアプリケーションでは、このメッセージはアプリケーションが誤ってオブジェクトへの参照を保持しているため、オブジェクトのガベージ・コレクションができないことを示している可能性があります。これは、Java言語ではメモリー・リークに相当します。
ノート:
アプリケーションによって呼び出されたAPIが、誤ってオブジェクト参照を保持している可能性もあります。このエラーのもう1つの潜在的な原因は、ファイナライザを過度に使用するアプリケーションで発生します。クラスに
finalize
メソッドがある場合、そのタイプのオブジェクトの空間はガベージ・コレクション時に再利用されません。かわりに、ガベージ・コレクション後にファイナライズ用のキューにオブジェクトが入れられ、後でファイナライズが実行されます。Oracle Sunの実装では、ファイナライザはファイナライズ・キューを提供するデーモン・スレッドによって実行されます。ファイナライザ・スレッドがファイナライズ・キューを処理しきれなかった場合は、Javaヒープがいっぱいになる可能性があり、このタイプのOutOfMemoryError
例外がスローされます。この状況を発生させるシナリオの1つは、アプリケーションが優先度の高いスレッドを作成しているため、ファイナライザ・スレッドがファイナライズ・キューを処理する速度よりキューの増加速度の方が速くなっている場合です。 - Exception in thread thread_name: java.lang.OutOfMemoryError: GC Overhead limit exceeded
-
原因: 「GC overhead limit exceeded」という詳細メッセージは、ガベージ・コレクタが常時実行されているため、Javaプログラムの処理がほとんど進んでいないことを示しています。ガベージ・コレクション後、Javaプロセスでガベージ・コレクションの実行に約98%を超える時間が費やされ、ヒープのリカバリが2%未満であり、その状態が直近5回(コンパイル時間定数)の連続するガベージ・コレクションで続いている場合に、
java.lang.OutOfMemoryError
がスローされます。この例外は通常、Javaヒープに新しい割当て用の空き領域がわずかしかなく、ライブ・データの容量をほとんど格納できないためにスローされます。 - Exception in thread thread_name: Requested array size exceeds VM limit
-
原因: リクエストされた配列サイズはVMの制限を超えていますという詳細メッセージは、使用可能なヒープ・サイズに関係なく、アプリケーション(またはアプリケーションが使用するAPI)がVM実装の制限より大きいサイズの配列を割り当てようとしたことを示します。
- Exception in thread thread_name: java.lang.OutOfMemoryError: Metaspace
-
原因: Javaクラス・メタデータ(Javaクラスの仮想マシン内部表現)がネイティブ・メモリー(ここでは、メタスペースと呼ばれる)に割り当てられています。クラス・メタデータのメタスペースが枯渇すると、「
MetaSpace
」という詳細のjava.lang.OutOfMemoryError
例外がスローされます。クラス・メタデータに使用できるメタスペース容量は、コマンド行に指定するMaxMetaSpaceSize
パラメータにより制限されます。クラス・メタデータに必要なネイティブ・メモリー容量がMaxMetaSpaceSize
を超過すると、MetaSpace
詳細とともにjava.lang.OutOfMemoryError
例外がスローされます。 - Exception in thread thread_name: java.lang.OutOfMemoryError: request size bytes for reason.スワップ領域が不足していますか。
-
原因: 「request size bytes for reason.Out of swap space?」という詳細メッセージは、
OutOfMemoryError
例外であるように見えます。しかし、ネイティブ・ヒープからの割当てが失敗し、ネイティブ・ヒープが枯渇寸前になっている可能性があるときに、Java HotSpot VMコードはこの見かけ上の例外を報告します。このメッセージは、失敗した要求のサイズ(バイト数)とメモリー要求の理由を示しています。通常、その理由は割当ての失敗を報告するソース・モジュールの名前になりますが、場合によっては実際の理由になることもあります。 - Exception in thread thread_name: java.lang.OutOfMemoryError: Compressed class space
-
原因: 64ビット・プラットフォーム上で、クラス・メタデータへのポインタが32ビットのオフセットで表現されている可能性があります(
UseCompressedOops
を使用)。これはコマンド行フラグUseCompressedClassPointers
で制御されます(デフォルトの場合)。UseCompressedClassPointers
が使用されている場合、クラス・メタデータに使用できる領域量はCompressedClassSpaceSize
の容量で固定されます。UseCompressedClassPointers
に必要な領域がCompressedClassSpaceSize
を超える場合、「Compressed class space」という詳細のjava.lang.OutOfMemoryError
がスローされます。 - Exception in thread thread_name: java.lang.OutOfMemoryError: reason stack_trace_with_native_method
-
原因: エラー・メッセージの詳細部分が「reason stack_trace_with_native_method」であり、最上位フレームがネイティブ・メソッドであるスタック・トレースが出力される場合、これはネイティブ・メソッドで割当て失敗が発生したことを示しています。これと前のメッセージとの違いは、割当ての失敗がJVMコードではなく、Java Native Interface (JNI)またはネイティブ・メソッドで検出されたことです。
OutOfMemoryErrorのかわりにクラッシュのトラブルシューティング
致命的エラー・ログやクラッシュ・ダンプの情報を使用して、クラッシュをトラブルシューティングします。
ネイティブ・ヒープからの割当てが失敗した直後に、アプリケーションがクラッシュすることがあります。これは、メモリー割当て関数によって返されたエラーをチェックしないネイティブ・コードで発生します。
たとえば、malloc
システム・コールは使用可能なメモリーがない場合にnull
を返します。malloc
からの戻り値がチェックされない場合、アプリケーションは無効なメモリー位置にアクセスしようとしてクラッシュする可能性があります。状況によっては、このタイプの問題を特定することが難しいことがあります。
しかし、致命的エラー・ログやクラッシュ・ダンプの情報があれば、この問題を十分に診断できる場合もあります。致命的エラー・ログについては、「致命的エラー・ログ」で詳しく説明されています。割当ての失敗によってクラッシュが生じた場合は、割当ての失敗の理由を調べてください。ネイティブ・ヒープに関する他の問題と同様に、システムに十分な容量のスワップ空間が構成されていなかったり、システム上の別のプロセスがすべてのメモリー・リソースを消費していたり、システムのメモリー不足を引き起こすリークがアプリケーション内(またはそこから呼び出されたAPI内)で発生していたりする可能性があります。
Java言語コードでのリークの診断
NetBeansプロファイラを使用してJava言語コードのリークを診断します。
Java言語コード内のリークの診断は難しい可能性があります。通常、アプリケーションの非常に詳しい知識が必要です。さらに、プロセスが反復し、冗長なことがよくあります。この項では、Java言語コード内のメモリー・リークを診断するために使用できるツールについて説明します。
ノート:
この項で説明したツールに加えて、数多くのサードパーティ・メモリー・デバッガ・ツールが利用可能です。メモリー・デバッグ機能を備えた商用ツールの例として、Eclipse Memory AnalyzerとYourKitの2つがあります。他にも数多くありますが、推奨される特定の製品はありません。
Java言語コードのリークの診断に使用されるユーティリティを次に示します。
次の項では、Java言語コードでのリークを診断するためのその他の方法について説明します。
ヒープ・ヒストグラムの取得
使用可能な各種コマンドおよびオプションを使用して、メモリー・リークを特定するためのヒープ・ヒストグラムを取得します。
ヒープ・ヒストグラムを調べることで、メモリー・リークをすばやく絞り込んでみることができます。ヒープ・ヒストグラムはいくつかの方法で取得できます。
ファイナライズを保留中のオブジェクトのモニター
ファイナライズが保留されているオブジェクトをモニターするには、様々なコマンドやオプションを使用できます。
OutOfMemoryError例外が「Java heap space」という詳細メッセージとともにスローされた場合、その原因はファイナライザの過度の使用によるものである可能性があります。これを診断するために、ファイナライズを保留しているオブジェクトの数をモニターするためのオプションが複数用意されています。
- JConsole管理ツールを使用して、ファイナライズを保留中のオブジェクト数をモニターできます。このツールでは、「サマリー」タブ・ペインのメモリー統計にファイナライズ保留中の数が報告されます。この数は概算ですが、アプリケーションを特徴付け、アプリケーションがファイナライズに大きく依存しているかどうかを理解するために使用できます。
- Oracle SolarisおよびLinuxオペレーティング・システムでは、
jmap
ユーティリティを-finalizerinfo
オプションとともに使用することで、ファイナライズを待機しているオブジェクトに関する情報を出力できます。 - アプリケーションで、
java.lang.management.MemoryMXBean
クラスのgetObjectPendingFinalizationCount
メソッドを使用して、ファイナライズを保留しているオブジェクトのおよその数を報告できます。APIドキュメントへのリンクおよびコード例は、「カスタム診断ツール」に記載されています。コード例を拡張して、ファイナライズ保留中の数の報告を簡単に追加できます。
ネイティブ・コードでのリークの診断
複数の手法を使用してネイティブ・コードのメモリー・リークを検出し、切り分けることができます。一般に、すべてのプラットフォームに対応できる理想的な解決方法はありません。
ネイティブ・コードを診断するためのいくつかの手法を次に示します。
すべてのメモリー割当ておよび解放呼出しの追跡
ツールを使用して、すべてのメモリー割当ておよびそのメモリーの使用方法を追跡できます。
非常によく使われる方法は、ネイティブ割当てのすべての割当て呼び出しと解放呼出しを追跡することです。これは、かなり簡単なプロセスになる場合と、非常に高度なプロセスになる場合があります。ネイティブ・ヒープの割り当てとそのメモリーの使用の追跡をめぐっては、長年にわたって多くの製品が構築されています。
IBM Rational PurifyのようなツールやSun Studioのdbx
デバッガの実行時チェック機能を使用すると、ネイティブ・コードの通常の状況でこれらのリークを検出したり、初期化されていないメモリーへの割当てや解放されたメモリーへのアクセスを表すネイティブ・ヒープ・メモリーへのアクセスを検出したりできます。「dbxデバッガによるリークの検出」を参照してください。
これらのタイプのツールのすべてがネイティブ・コードを使用するJavaアプリケーションで機能するわけではなく、これらのツールは通常はプラットフォーム固有です。仮想マシンはコードを実行時に動的に作成するため、これらのツールはコードを誤って解釈したり、まったく動作しなかったり、誤った情報を与えたりする可能性があります。ツールのベンダーに問い合せて、ツールのバージョンが、使用している仮想マシンのバージョンで動作することを確認してください。
多数の簡単で移植可能なネイティブ・メモリー・リークの検出例は、sourceforgeを参照してください。ほとんどのライブラリやツールは、アプリケーションのソースを再コンパイルまたは編集して、割当て関数にラッパー関数を被せることができることを前提としています。より強力なツールでは、これらの割当て関数に動的に介入することで、アプリケーションを変更せずに実行できます。これは、Oracle Solaris 9オペレーティング・システムupdate 3で最初に導入されたlibumem.so
ライブラリのケースです(「libumemツールによるリークの検出」を参照)。
JNIライブラリのすべてのメモリー割当ての追跡
JNIライブラリを記述する場合は、簡単なラッパー・アプローチを使用して、ライブラリでメモリー・リークが発生しないことを保証する局所的な方法の作成を検討してください。
次の例の手順は、JNIライブラリ用の簡単な局所的割当て追跡方法です。まず、すべてのソース・ファイルに次の行を定義します。
#include <stdlib.h>
#define malloc(n) debug_malloc(n, __FILE__, __LINE__)
#define free(p) debug_free(p, __FILE__, __LINE__)
これにより、次の例の関数を使用してリークを監視できます。
/* Total bytes allocated */
static int total_allocated;
/* Memory alignment is important */
typedef union { double d; struct {size_t n; char *file; int line;} s; } Site;
void *
debug_malloc(size_t n, char *file, int line)
{
char *rp;
rp = (char*)malloc(sizeof(Site)+n);
total_allocated += n;
((Site*)rp)->s.n = n;
((Site*)rp)->s.file = file;
((Site*)rp)->s.line = line;
return (void*)(rp + sizeof(Site));
}
void
debug_free(void *p, char *file, int line)
{
char *rp;
rp = ((char*)p) - sizeof(Site);
total_allocated -= ((Site*)rp)->s.n;
free(rp);
}
その場合、JNIライブラリは定期的(またはシャットダウン時)にtotal_allocated
変数の値をチェックして、それが妥当であることを確認する必要があります。前述のコードを拡張して、残された割当てをリンク・リストに保存し、リークしたメモリーがどこで割り当てられたかを報告することもできます。これは、単一セットのソース内のメモリー割り当てを追跡するための、局所的で移植可能な方法です。debug_free()がdebug_malloc()に由来するポインタのみを指定して呼び出されたことを確認する必要があり、realloc()、calloc()、strdup()などが使用されていた場合は、これらに対しても同じような関数を作成する必要があります。
より大域的な方法でネイティブ・ヒープのメモリー・リークを検出するには、プロセス全体のライブラリ呼び出しへの介入が必要になります。
オペレーティング・システム・サポートによるメモリー割当ての追跡
ツールを使用して、オペレーティング・システムのメモリー割当てを追跡できます。
ほとんどのオペレーティング・システムには、なんらかの形式の大域的割り当て追跡サポートが含まれています。
- Windowsでは、MSDNライブラリでデバッグ・サポートを検索してください。Microsoft C++コンパイラには、メモリー割当てを追跡するための追加サポートを自動的に取り込む、
/Md
および/Mdd
コンパイラ・オプションがあります。 - Linuxシステムには、割り当て追跡を扱う際に役立つ、
mtrace
やlibnjamd
などのツールがあります。 - Oracle Solarisオペレーティング・システムには、
watchmalloc
ツールが用意されています。Oracle Solaris 9オペレーティング・システムUpdate 3では、libumem
ツールも提供されています。「libumemツールによるリークの検出」を参照してください。
dbxデバッガによるリークの検出
dbx
デバッガには、リークを検出できる実行時チェック(RTC)機能が含まれています。dbx
デバッガはOracle Solaris Studioの一部で、Linuxでも使用できます。
次の例はdbx
セッションのサンプルです。
$ dbx ${java_home}/bin/java
Reading java
Reading ld.so.1
Reading libthread.so.1
Reading libdl.so.1
Reading libc.so.1
(dbx) dbxenv rtc_inherit on
(dbx) check -leaks
leaks checking - ON
(dbx) run HelloWorld
Running: java HelloWorld
(process id 15426)
Reading rtcapihook.so
Reading rtcaudit.so
Reading libmapmalloc.so.1
Reading libgen.so.1
Reading libm.so.2
Reading rtcboot.so
Reading librtc.so
RTC: Enabling Error Checking...
RTC: Running program...
dbx: process 15426 about to exec("/net/bonsai.sfbay/export/home2/user/ws/j2se/build/solaris-i586/bin/java")
dbx: program "/net/bonsai.sfbay/export/home2/user/ws/j2se/build/solaris-i586/bin/java"
just exec'ed
dbx: to go back to the original program use "debug $oprog"
RTC: Enabling Error Checking...
RTC: Running program...
t@1 (l@1) stopped in main at 0x0805136d
0x0805136d: main : pushl %ebp
(dbx) when dlopen libjvm { suppress all in libjvm.so; }
(2) when dlopen libjvm { suppress all in libjvm.so; }
(dbx) when dlopen libjava { suppress all in libjava.so; }
(3) when dlopen libjava { suppress all in libjava.so; }
(dbx) cont
Reading libjvm.so
Reading libsocket.so.1
Reading libsched.so.1
Reading libCrun.so.1
Reading libm.so.1
Reading libnsl.so.1
Reading libmd5.so.1
Reading libmp.so.2
Reading libhpi.so
Reading libverify.so
Reading libjava.so
Reading libzip.so
Reading en_US.ISO8859-1.so.3
hello world
hello world
Checking for memory leaks...
Actual leaks report (actual leaks: 27 total size: 46851 bytes)
Total Num of Leaked Allocation call stack
Size Blocks Block
Address
========== ====== =========== =======================================
44376 4 - calloc < zcalloc
1072 1 0x8151c70 _nss_XbyY_buf_alloc < get_pwbuf < _getpwuid <
GetJavaProperties < Java_java_lang_System_initProperties <
0xa740a89a< 0xa7402a14< 0xa74001fc
814 1 0x8072518 MemAlloc < CreateExecutionEnvironment < main
280 10 - operator new < Thread::Thread
102 1 0x8072498 _strdup < CreateExecutionEnvironment < main
56 1 0x81697f0 calloc < Java_java_util_zip_Inflater_init < 0xa740a89a<
0xa7402a6a< 0xa7402aeb< 0xa7402a14< 0xa7402a14< 0xa7402a14
41 1 0x8072bd8 main
30 1 0x8072c58 SetJavaCommandLineProp < main
16 1 0x806f180 _setlocale < GetJavaProperties <
Java_java_lang_System_initProperties < 0xa740a89a< 0xa7402a14<
0xa74001fc< JavaCalls::call_helper < os::os_exception_wrapper
12 1 0x806f2e8 operator new < instanceKlass::add_dependent_nmethod <
nmethod::new_nmethod < ciEnv::register_method <
Compile::Compile #Nvariant 1 < C2Compiler::compile_method <
CompileBroker::invoke_compiler_on_method <
CompileBroker::compiler_thread_loop
12 1 0x806ee60 CheckJvmType < CreateExecutionEnvironment < main
12 1 0x806ede8 MemAlloc < CreateExecutionEnvironment < main
12 1 0x806edc0 main
8 1 0x8071cb8 _strdup < ReadKnownVMs < CreateExecutionEnvironment < main
8 1 0x8071cf8 _strdup < ReadKnownVMs < CreateExecutionEnvironment < main
この出力は、プロセスが終了しようとしている時点でメモリーが解放されない場合、dbx
デバッガがメモリー・リークを報告することを示しています。ただし、初期化時に割り当てられ、プロセスが終了するまで必要となるメモリーは、多くの場合、ネイティブ・コードでは解放されません。したがって、そのような場合は、dbx
デバッガが実際にはリークではないメモリー・リークを報告することがあります。
ノート:
前の例では、仮想マシンlibjvm.so
とJavaサポート・ライブラリlibjava.so
で報告されたリークを抑制するために、2つのsuppress
コマンドを使用しています。
libumemツールによるリークの検出
libumem.so
ライブラリとモジュラ・デバッガmdb
は、最初にOracle Solaris 9オペレーティング・システムupdate 3で導入され、メモリー・リークをデバッグするために使用できます。
libumem
を使用する前に、次の例に示すように、libumem
ライブラリをプリロードし、環境変数を設定する必要があります。
$ LD_PRELOAD=libumem.so
$ export LD_PRELOAD
$ UMEM_DEBUG=default
$ export UMEM_DEBUG
ここで、Javaアプリケーションを実行しますが、それを終了する前に停止します。次の例では、_exit
システム・コールが呼び出されたときにtruss
を使用してプロセスを停止しています。
$ truss -f -T _exit java MainClass arguments
この時点で、次の例に示すように、mdb
デバッガを接続できます。
$ mdb -p
pid
>::findleaks
::findleaks
コマンドは、mdb
でメモリー・リークを検出するコマンドです。リークが検出されたら、このコマンドによって割当て呼出しのアドレス、バッファ・アドレス、および最も近いシンボルを出力します。
bufctl
構造体をダンプして、メモリー・リークが発生した割当てのスタック・トレースを取得することもできます。この構造体のアドレスは、::findleaks
コマンドの出力から取得できます。
メモリー・リークの原因のトラブルシューティングについては、libumem
を使用したメモリー・リークの分析を参照してください。