印刷ビューの終了

HotSpot VM のトラブルシューティングガイド

印刷ビュー

ドキュメント情報

はじめに

1. 診断ツールおよびオプション

2. ツールの詳細な説明

3. メモリーリークのトラブルシューティング

3.1 OutOfMemoryError の意味

3.1.1 詳細メッセージ: Java heap space

3.1.2 詳細メッセージ: PermGen space

3.1.3 詳細メッセージ: Requested array size exceeds VM limit

3.1.4 詳細メッセージ: request

3.1.5 詳細メッセージ:

3.2 OutOfMemoryError ではなくクラッシュ

3.3 Java 言語コード内のリークの診断

3.3.1 NetBeans Profiler

3.3.2 jhat ユーティリティーの使用

3.3.3 ヒープダンプの作成

3.3.3.1 HPROF プロファイラ

3.3.3.2 jmap ユーティリティー

3.3.3.3 JConsole ユーティリティー

3.3.3.4 -XX:+HeapDumpOnOutOfMemoryError コマンド行オプション

3.3.4 実行中のプロセスのヒープヒストグラムの取得

3.3.5 OutOfMemoryError 時のヒープヒストグラムの取得

3.3.6 ファイナライズ保留中のオブジェクト数のモニタリング

3.3.7 サードパーティーのメモリーデバッガ

3.4 ネイティブコード内のリークの診断

3.4.1 すべてのメモリー割り当てと解放呼び出しの追跡

3.4.2 JNI ライブラリ内のメモリー割り当ての追跡

3.4.3 OS サポートによるメモリー割り当ての追跡

3.4.4 dbx を使ったリークの検出

3.4.5 libumem を使ったリークの検出

4. システムクラッシュのトラブルシューティング

5. ハングアップまたはループしているプロセスのトラブルシューティング

6. シグナル処理と例外処理の統合

7. バグレポートの提出

A. 環境変数とシステムプロパティー

B. コマンド行オプション

C. 致命的エラーログ

D. このリリースのツールのサマリー

第 3 章

メモリーリークのトラブルシューティング

アプリケーションの実行時間がしだいに長くなる場合や、オペレーティングシステムの実行速度がしだいに低下しているように思われる場合、これはメモリーリークの兆候である可能性があります。つまり、仮想メモリーが割り当てられているけれども、それが不要になったときに返されません。最終的には、アプリケーションまたはシステムがメモリーを使い果たし、アプリケーションが異常終了します。

この章では、メモリーリークが関与する問題の診断について、いくつかのアドバイスを行います。

3.1 OutOfMemoryError の意味

メモリーリークの共通の兆候の 1 つは、java.lang.OutOfMemoryError エラーです。このエラーは、Java ヒープまたはヒープの特定の領域にオブジェクトを割り当てるための十分な空間がないときにスローされます。ガベージコレクタによって新しいオブジェクトを格納するための空間をこれ以上確保することも、ヒープをこれ以上拡張することもできません。

java.lang.OutOfMemoryError エラーがスローされると、スタックトレースも出力されます。

java.lang.OutOfMemoryError は、ネイティブ割り当てを満たすことができないとき (たとえば、スワップ空間が少ない場合) に、ネイティブライブラリコードによってスローされることもあります。

OutOfMemoryError を診断する最初の手順は、エラーの意味を知ることです。Java ヒープがいっぱいであることを意味するのか、またはネイティブヒープがいっぱいであることを意味するのか。この質問に答えられるように、以降のサブセクションでは、考えられるエラーメッセージの一部について説明し、メッセージの詳細部分を参照します。

3.1.1 詳細メッセージ: Java heap space

詳細メッセージ Java heap space は、Java ヒープ内でオブジェクトを割り当てることができなかったことを示します。このエラーは、必ずしもメモリーリークを意味しません。この問題は、指定されたヒープサイズ (指定されていない場合はデフォルトサイズ) がアプリケーションにとって十分でないという、単純な構成の問題である可能性があります。

それ以外の場合、特に長期にわたって稼動するアプリケーションでは、このメッセージはアプリケーションが誤ってオブジェクトへの参照を保持しているため、オブジェクトのガベージコレクションができないことを示している可能性があります。これは、Java 言語ではメモリーリークに相当します。アプリケーションによって呼び出された API が誤ってオブジェクト参照を保持している可能性もあります。

OutOfMemoryError のもう 1 つの潜在的な原因は、ファイナライザを過度に使用するアプリケーションで発生します。クラスに finalize メソッドがある場合、そのタイプのオブジェクトの空間はガベージコレクション時に再利用されません。代わりに、ガベージコレクション後にファイナライズ用のキューにオブジェクトが入れられ、あとでファイナライズが実行されます。Sun の実装では、ファイナライザはファイナライズキューを提供するデーモンスレッドによって実行されます。ファイナライザスレッドがファイナライズキューを処理しきれなかった場合は、Java ヒープがいっぱいになる可能性があり、OutOfMemoryError がスローされます。この状況を発生させるシナリオの 1 つは、アプリケーションが優先度の高いスレッドを作成しているため、ファイナライザスレッドがファイナライズキューを処理する速度よりキューの増加速度の方が速くなっている場合です。セクション「3.3.6 ファイナライズ保留中のオブジェクト数のモニタリング」では、ファイナライズが保留されているオブジェクトをモニターする方法について説明します。

「3.1.2 詳細メッセージ: PermGen space

詳細メッセージ PermGen space は、Permanent 世代がいっぱいであることを示します。Permanent 世代とは、ヒープ内で、クラスやメソッドのオブジェクトが格納される領域のことです。非常に多数のクラスをロードするアプリケーションでは、-XX:MaxPermSize オプションを使って Permanent 世代のサイズを増やす必要がある場合があります。

intern された java.lang.String オブジェクトは、Permanent 世代に格納されなくなります。java.lang.String クラスは文字列のプールを保持します。intern メソッドを呼び出すと、このメソッドはプールをチェックし、同等の文字列がすでにプール内に存在するかどうかを確認します。存在する場合、intern メソッドはそれを返し、そうでない場合はその文字列をプールに追加します。より正確には、java.lang.String.intern メソッドを使用して文字列の正規表現が取得されます。この結果は、その文字列がリテラルとして表示された場合に返される同じクラスインスタンスへの参照です。

この種類のエラーが発生した場合は、出力されるスタックトレースの最上部近くにテキスト ClassLoader.defineClass が表示されます。

jmap -permgen コマンドは、Permanent 世代に含まれるオブジェクトの統計を出力し、内部化された String インスタンスに関する情報も出力します。「2.7.4 Permanent 世代に関する情報の取得」を参照してください。

3.1.3 詳細メッセージ: Requested array size exceeds VM limit

詳細メッセージ Requested array size exceeds VM limit は、アプリケーション (またはそのアプリケーションが使用する API) がヒープサイズより大きい配列を割り当てようとしたことを示します。たとえば、アプリケーションが 512M バイトの配列を割り当てようとしたが、最大ヒープサイズが 256M バイトだった場合は、理由 Requested array size exceeds VM limit とともに OutOfMemoryError がスローされます。ほとんどの場合、この問題は構成の問題 (ヒープサイズが小さすぎる) であるか、アプリケーションが非常に大きい配列を作成しようとする結果をもたらすバグ (たとえば、不正なサイズを計算するアルゴリズムを使用して配列内の要素の数が計算された場合など) です。

3.1.4 詳細メッセージ: request

詳細メッセージ: request は、OutOfMemoryError のように見えます。しかし、HotSpot VM コードは、ネイティブヒープからの割り当てが失敗し、ネイティブヒープがほぼ使い果たされている可能性がある場合に、この見かけ上の例外を報告します。このメッセージは、失敗したリクエストのサイズ (バイト単位) と、メモリーリクエストの理由を示します。ほとんどの場合、メッセージの の部分は割り当ての失敗を報告したソースモジュールの名前ですが、理由を示している場合もあります。

このエラーメッセージがスローされると、VM は致命的エラー処理メカニズムを呼び出します (つまり、クラッシュ発生時のスレッド、プロセス、およびシステムに関する有用な情報を含む、致命的エラーログファイルを生成します)。ネイティブヒープ不足の場合は、ログ内のヒープメモリーとメモリーマップの情報が役立つ可能性があります。このファイルの詳細は、付録 C「致命的エラーログ」を参照してください。

このタイプの OutOfMemoryError がスローされた場合は、オペレーティングシステムのトラブルシューティングユーティリティーを使用して、問題をより詳しく診断する必要がある場合があります。「2.16 オペレーティングシステム固有のツール」を参照してください。

問題がアプリケーションに関連していない可能性もあります。例を示します。

  • オペレーティングシステムに十分なスワップ空間が構成されていない。

  • システム上の別のプロセスがすべてのメモリーリソースを消費している。

上記のどちらの問題も原因でない場合は、ネイティブのリークのためにアプリケーションが失敗した可能性があります (たとえば、アプリケーションまたはライブラリのコードが継続的にメモリーを割り当てているが、それをオペレーティングシステムに解放していない場合)。

3.1.5 詳細メッセージ:

エラーメッセージの詳細部分が であり、最上位フレームがネイティブメソッドであるスタックトレースが出力される場合、これはネイティブメソッドで割り当て失敗が発生したことを示しています。これと前のメッセージとの違いは、割り当ての失敗が Java VM コードではなく、JNI またはネイティブメソッドで検出されたことです。

このタイプの OutOfMemoryError がスローされた場合は、オペレーティングシステムのユーティリティーを使用して、問題をより詳しく診断する必要がある場合があります。「2.16 オペレーティングシステム固有のツール」を参照してください。

3.2 OutOfMemoryError ではなくクラッシュ

ネイティブヒープからの割り当てが失敗した直後に、アプリケーションがクラッシュすることがあります。これは、メモリー割り当て関数によって返されたエラーをチェックしないネイティブコードで発生します。

たとえば、malloc システムコールは使用可能なメモリーがない場合に NULL を返します。malloc からの戻り値がチェックされない場合、アプリケーションは無効なメモリー位置にアクセスしようとしてクラッシュする可能性があります。状況によっては、このタイプの問題を特定することが難しいことがあります。

しかし、致命的エラーログやクラッシュダンプの情報があれば、この問題を十分に診断できる場合もあります。致命的エラーログについては、付録 C「致命的エラーログ」で詳しく説明されています。割り当ての失敗をチェックしていないことがクラッシュの原因であるとわかった場合は、割り当ての失敗の理由を調べる必要があります。ネイティブヒープに関するほかの問題と同様に、システムに十分なスワップ空間が構成されていなかったり、システム上の別のプロセスがすべてのメモリーリソースを消費していたり、システムのメモリー不足を引き起こすリークがアプリケーション内 (またはそこから呼び出された API 内) で発生していたりする可能性があります。

3.3 Java 言語コード内のリークの診断

Java 言語コード内のリークの診断は、難しいタスクになる可能性があります。ほとんどの場合、アプリケーションの非常に詳しい知識が必要です。さらに、プロセスが反復し、冗長なことがよくあります。このセクションには、次のサブセクションがあります。

3.3.1 NetBeans Profiler

NetBeans Profiler (従来は JFluid と呼ばれていた) は、メモリーリークを迅速に特定できる優れたプロファイラです。商用のメモリーリークデバッグツールのほとんどは、多くの場合、大規模なアプリケーション内のリークを特定するのに長い時間がかかる場合があります。しかし、NetBeans Profiler は、このようなオブジェクトが一般的に示すメモリーの割り当てと再利用のパターンを使用します。このプロセスには、メモリー再利用の欠落も含まれます。このプロファイラは、これらのオブジェクトがどこで割り当てられたかをチェックできます (リークの根本原因を特定するには、多くの場合、これで十分です)。

詳細は、http://profiler.netbeans.org で見つけることができます。

3.3.2 jhat ユーティリティーの使用

jhat ユーティリティー (「2.5 jhat ユーティリティー」を参照) は、意図しないオブジェクトの保持 (またはメモリーリーク) をデバッグするときに役立ちます。オブジェクトダンプを参照し、ヒープ内の到達可能なすべてのオブジェクトを表示し、どの参照がオブジェクトを保持しているかを理解するための方法を提供します。

jhat を使用するには、実行中のアプリケーションのヒープダンプを 1 つ以上取得する必要があり、ダンプはバイナリ形式である必要があります。ダンプを作成したあとは、「2.5 jhat ユーティリティー」で説明しているように、それを jhat の入力として使用できます。

3.3.3 ヒープダンプの作成

ヒープダンプは、ヒープメモリーの割り当てに関する詳細情報を提供します。以降のセクションでは、ヒープダンプを作成するいくつかの方法について説明します。

3.3.3.1 HPROF プロファイラ

HPROF プロファイラエージェントは、アプリケーションの実行中にヒープダンプを作成できます。コマンド行の例を次に示します。

$ java -agentlib:hprof=file=snapshot.hprof,format=b application

VM が埋め込まれているか、追加オプションを指定できるコマンド行起動ツールを使用して起動されていない場合は、JAVA_TOOLS_OPTIONS 環境変数を使用して -agentlib オプションをコマンド行に自動的に追加できる可能性があります。この環境変数の詳細は、「A.2 JAVA_TOOL_OPTIONS 環境変数」を参照してください。

アプリケーションが HPROF とともに実行されている場合は、アプリケーションコンソールで (プラットフォームに応じて) Ctrl + \ または Ctrl + Break を押すことでヒープダンプが作成されます。Solaris OS および Linux では、代わりに kill -QUIT pid コマンドを使用して QUIT シグナルを送信する方法もあります。このシグナルを受信すると、ヒープダンプが作成されます。上の例では、snapshot.hprof ファイルが作成されます。

ヒープダンプファイルには、すべてのプリミティブデータとスタックトレースが含まれています。

1 つのダンプファイルに複数のヒープダンプを含めることができます。Ctrl + \ または Ctrl + Break を何度も押すと、後続のダンプがファイルの末尾に追加されます。jhat ユーティリティーでは、#n 構文 (n はダンプの番号) を使用してダンプを区別します。

3.3.3.2 jmap ユーティリティー

jmap ユーティリティー (「2.7 jmap ユーティリティー」を参照) を使用してヒープダンプを取得することもできます。コマンド行の例を次に示します。

$ jmap -dump:format=b,file=snapshot.jmap process-pid

jmap ツールは、Java VM の起動方法に関係なくヒープダンプのスナップショット (上の例では snapshot.jmap という名前のファイルに) を生成します。jmap の出力ファイルにはすべてのプリミティブデータが含まれるはずですが、オブジェクトが作成された場所を示すスタックトレースは含まれません。

3.3.3.3 JConsole ユーティリティー

ヒープダンプを取得するもう 1 つの方法は、JConsole ユーティリティーを使用することです。「MBeans」タブで HotSpotDiagnostic MBean を選択すると、「Operations」が表示されるので、dumpHeap 操作を選択します。

3.3.3.4 -XX:+HeapDumpOnOutOfMemoryError コマンド行オプション

-XX:+HeapDumpOnOutOfMemoryError コマンド行オプションを指定すると、OutOfMemoryError がスローされた場合に VM がヒープダンプを生成します。

3.3.4 実行中のプロセスのヒープヒストグラムの取得

ヒープヒストグラムを調べることで、メモリーリークをすばやく絞り込んでみることができます。この情報は、複数の方法で取得できます。

  • jmap -histo pid コマンドを使用して、実行中のプロセスからヒープヒストグラムを取得できます。この出力には、ヒープ内の各クラス型の合計サイズとインスタンス数が表示されます。ヒストグラムを連続して (たとえば、2 分ごとに) 取得すると、詳細な分析につなげることができる傾向を観察できる場合があります。

  • Solaris OS および Linux では、jmap ユーティリティーを使用してコアファイルからヒストグラムを提供することもできます。

  • -XX:+PrintClassHistogram コマンド行オプションを指定して Java プロセスを起動すると、Ctrl + Break ハンドラによってヒープヒストグラムが生成されます。

3.3.5 OutOfMemoryError 時のヒープヒストグラムの取得

-XX:+HeapDumpOnOutOfMemoryError コマンド行オプションを指定すると、OutOfMemoryError がスローされた場合に VM がヒープダンプを生成します。その後、jmap ユーティリティーを使用してヒープダンプからヒストグラムを取得できます。

OutOfMemoryError がスローされたときにコアファイルが生成された場合は、次の例のようにコアファイルに対して jmap を実行することでヒストグラムを取得できます。

$ jmap -histo \ /java/re/javase/6/latest/binaries/solaris-sparc/bin/java core.27421

Attaching to core core.27421 from executable 
/java/re/javase/6/latest/binaries/solaris-sparc/bin/java, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 1.6.0-beta-b63
Iterating over heap. This may take a while...
Heap traversal took 8.902 seconds.

Object Histogram:
 
Size      Count   Class description
-------------------------------------------------------
86683872  3611828 java.lang.String
20979136  204     java.lang.Object[]
403728    4225    * ConstMethodKlass
306608    4225    * MethodKlass
220032    6094    * SymbolKlass
152960    294     * ConstantPoolKlass
108512    277     * ConstantPoolCacheKlass
104928    294     * InstanceKlassKlass
68024     362     byte[]
65600     559     char[]
31592     359     java.lang.Class
27176     462     java.lang.Object[]
25384     423     short[]
17192     307     int[]
:

この例は、OutOfMemoryError の原因が java.lang.String オブジェクトの数 (ヒープ内の 3611828 個のインスタンス) であることを示しています。文字列がどこで割り当てられたかは、詳細な分析を行わないとはっきりしません。それでもなおこの情報は有用で、HPROFjhat などのツールを使用して引き続き調査を行うことで、文字列がどこで割り当てられたかや、どの参照によってそれらが保持され、ガベージコレクトされなくなっているかを特定できます。

3.3.6 ファイナライズ保留中のオブジェクト数のモニタリング

「3.1.1 詳細メッセージ: Java heap spaceで説明したように、ファイナライザを過度に使用すると、OutOfMemoryError が発生することがあります。ファイナライズを保留しているオブジェクトの数をモニターするためのオプションが複数用意されています。

  • JConsole 管理ツール (「2.3 JConsole ユーティリティー」を参照) を使用して、ファイナライズを保留しているオブジェクトの数をモニターできます。このツールでは、「Summary」タブペインのメモリー統計に保留中のファイナライズの数が報告されます。この数は概算ですが、アプリケーションを特徴付け、アプリケーションがファイナライズに大きく依存しているかどうかを理解するために使用できます。

  • Solaris OS および Linux では、jmap -finalizerinfo オプションによって、ファイナライズを待機しているオブジェクトに関する情報が出力されます。

  • アプリケーションは、java.lang.management.MemoryMXBean クラスの getObjectPendingFinalizationCount メソッドを使用して、ファイナライズを保留しているオブジェクトのおよその数を報告できます。API ドキュメントとコード例へのリンクは、「2.17 診断ツールの開発」で見つけることができます。コード例を拡張して、保留中のファイナライズ数の報告を簡単に追加できます。

3.3.7 サードパーティーのメモリーデバッガ

前の章で説明したツールに加えて、数多くのサードパーティーメモリーデバッガが利用可能です。メモリーデバッグ機能を備えた商用ツールの例として、Quest Software 社の JProbe と Borland 社の OptimizeIt の 2 つがあります。ほかにも数多くありますが、推奨される特定の製品はありません。

3.4 ネイティブコード内のリークの診断

複数の手法を使用してネイティブコードのメモリーリークを検出し、切り分けることができます。一般に、すべてのプラットフォームに対応する単独の理想的な解決方法はありません。

3.4.1 すべてのメモリー割り当てと解放呼び出しの追跡

非常によく使われる方法は、ネイティブ割り当てのすべての割り当て呼び出しと解放呼び出しを追跡することです。これは、かなり簡単なプロセスになる場合と、非常に高度なプロセスになる場合があります。ネイティブヒープの割り当てとそのメモリーの使用の追跡をめぐっては、長年にわたって多くの製品が構築されています。

Purify や Sun の dbx の実行時チェック機能 (「3.4.4 dbx を使ったリークの検出」を参照) を使用すると、ネイティブコードの通常の状況におけるこれらのリークを検出したり、初期化されてないメモリーへの割り当てや解放されたメモリーへのアクセスを表すネイティブヒープメモリーへのアクセスを検出したりできます。

これらのタイプのツールのすべてがネイティブコードを使用する Java アプリケーションで機能するわけではなく、これらのツールは通常はプラットフォーム固有です。仮想マシンはコードを実行時に動的に作成するため、これらのツールはコードを誤って解釈したり、まったく動作しなかったり、誤った情報を与えたりする可能性があります。ツールのベンダーに問い合わせて、ツールのバージョンが、使用している仮想マシンのバージョンで動作することを確認してください。

http://sourceforge.net/ に、簡単で移植可能なネイティブメモリーリークの検出例が数多く見つかります。これらのライブラリやツールのほとんどは、アプリケーションのソースを再コンパイルまたは編集して、割り当て関数にラッパー関数を被せることができることを前提としています。より強力なツールでは、これらの割り当て関数に動的に介入することで、アプリケーションを変更せずに実行できます。これは、Solaris 9 OS update 3 で導入された libumem.so ライブラリにも当てはまります (「3.4.5 libumem を使ったリークの検出」を参照)。

3.4.2 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() などが使用されている場合は、これらに対しても同じような関数を作成する必要があります。

より大域的な方法でネイティブヒープのメモリーリークを検出するには、プロセス全体のライブラリ呼び出しへの介入が必要になります。

3.4.3 OS サポートによるメモリー割り当ての追跡

ほとんどのオペレーティングシステムには、なんらかの形式の大域的割り当て追跡サポートが含まれています。

  • Windows の場合は、http://msdn.microsoft.com/library/default.asp にアクセスしてデバッグサポートを検索してください。Microsoft C++ コンパイラには、メモリー割り当てを追跡するための追加サポートを自動的に取り込む、/Md および /Mdd コンパイラオプションがあります。

  • Linux システムには、割り当て追跡を扱う際に役立つ、mtracelibnjamd などのツールがあります。

  • Solaris オペレーティングシステムには、watchmalloc ツールが用意されています。Solaris 9 OS update 3 から libumem ツールの提供を開始しました (「3.4.5 libumem を使ったリークの検出」を参照)。

3.4.4 dbx を使ったリークの検出

Sun のデバッガ dbx には、リークを検出できる実行時チェック (RTC) 機能が含まれています。dbx デバッガは 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 コマンドを使用しました。

3.4.5 libumem を使ったリークの検出

Solaris 9 OS update 3 以降では、libumem.so ライブラリとモジュラーデバッガ (mdb) を使用してメモリーリークをデバッグできます。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 でメモリーリークを検出するコマンドです。リークが検出されたら、findleaks コマンドによって割り当て呼び出しのアドレス、バッファーアドレス、およびもっとも近いシンボルを出力します。

bufctl 構造体をダンプして、メモリーリークが発生した割り当てのスタックトレースを取得することもできます。この構造体のアドレスは、::findleaks コマンドの出力から取得できます。これらの機能を実行するコマンドの説明、および libumem を使ったメモリー管理バグの識別方法の詳細は、次のアドレスにあります: http://download.oracle.com/docs/cd/E19424-01/820-4814/geogv/index.html