Java Platform, Standard Editionトラブルシューティング・ガイド
目次      

3.4 Java言語コードでのリークの診断

Java言語コード内のリークの診断は難しい可能性があります。 通常、アプリケーションの非常に詳しい知識が必要です。 さらに、プロセスが反復し、冗長なことがよくあります。 この項では、Java言語コード内のメモリー・リークを診断するために使用できるツールについて説明します。


ノート:

この項で説明したツールに加えて、数多くのサードパーティ・メモリー・デバッガ・ツールが利用可能です。 メモリー・デバッグ機能を備えた商用ツールの例として、Eclipse Memory Analyzer Tool (MAT)とYourKit (www.yourkit.com)の2つがあります。 他にも数多くありますが、推奨される特定の製品はありません。

Java言語コードのリークの診断に使用される2つのユーティリティを次に示します。

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

    詳細は、「NetBeansプロファイラ」を参照してください。

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

    jhatを使用するには、実行中のアプリケーションのヒープ・ダンプを1つ以上取得する必要があり、ダンプはバイナリ形式である必要があります。 作成されたダンプ・ファイルは、jhatへの入力として使用できます。 jhatユーティリティを参照してください。

次の項では、Java言語コードでのリークを診断するためのその他の方法について説明します。

3.4.1 ヒープ・ダンプの作成

ヒープ・ダンプは、ヒープ・メモリーの割当てに関する詳細情報を提供します。 ヒープ・ダンプを作成するには、いくつかの方法があります。

  • HPROFは、アプリケーションとともに起動された場合にヒープ・ダンプを作成できます。 例3-1に、コマンドの使用方法を示します。

    例3-1 アプリケーションでのHPROFの起動

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

    ノート:

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

    アプリケーションがHPROFとともに実行されていれば、アプリケーション・コンソールで[Ctrl]+[\]または[Ctrl]+[Break](プラットフォームによる)を押すことで、ヒープ・ダンプを作成できます。 Oracle SolarisおよびLinuxオペレーティング・システムでの代替アプローチには、kill -QUIT pidコマンドでQUITシグナルを送信する方法があります。 このシグナルが受信されると、指定されたPIDを持つプロセスのヒープ・ダンプが作成されます。前述の例では、snapshot.hprofファイルが作成されます。

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

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

  • jmapユーティリティを使用すると、実行中のプロセスのヒープ・ダンプを作成できます。

    診断機能を強化し、パフォーマンスのオーバーヘッドを削減するには、jmapユーティリティのかわりに最新のユーティリティjcmdの使用をお薦めします。 jcmdユーティリティの便利なコマンドを参照してください。 例3-2のコマンドでは、jcmdを使用して実行中のプロセスのヒープ・ダンプを作成し、例3-3jmapコマンドと同じような結果が得られます。

    例3-2 jcmdによるヒープ・ダンプの作成

    jcmd <process id/main class> GC.heap_dump filename=Myheapdump
    

    例3-3 jmapによるヒープ・ダンプの作成

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

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

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

  • アプリケーションの実行中に-XX:+HeapDumpOnOutOfMemoryErrorコマンド行オプションを指定すると、OutOfMemoryError例外がスローされたときに、JVMによってヒープ・ダンプが生成されます。

3.4.2 ヒープ・ヒストグラムの取得

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

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

  • jmapユーティリティを使用して、実行中のプロセスからヒープ・ヒストグラムを取得できます。

    診断機能を強化し、パフォーマンスのオーバーヘッドを削減するには、jmapユーティリティのかわりに最新のユーティリティjcmdの使用をお薦めします。 jcmdユーティリティの便利なコマンドを参照してください。例3-4のコマンドでは、jcmdを使用して実行中のプロセスのヒープ・ヒストグラムを作成し、jmapコマンドと同じような結果が得られます。

    例3-4 jcmdによるヒープ・ヒストグラムの作成

    jcmd <process id/main class> GC.class_histogram filename=Myheaphistogram
    
    jmap -histo pid
    

    この出力には、ヒープ内の各クラス型の合計サイズとインスタンス数が表示されます。 ヒストグラムを連続して(たとえば、2分ごとに)取得すると、詳細な分析につなげることができる傾向を観察できる場合があります。

  • 例3-5に示すように、jmapユーティリティを使用してコア・ファイルからヒープ・ヒストグラムを取得できます。

    例3-5 jmapによるヒープ・ヒストグラムの作成

    jmap -histo core_file
    

    たとえば、アプリケーションの実行中に-XX:+HeapDumpOnOutOfMemoryErrorコマンド行オプションを指定すると、OutOfMemoryError例外がスローされたときに、JVMによってヒープ・ダンプが生成されます。 その後、例3-6に示すように、コア・ファイルでjmapを実行してヒストグラムを取得できます。

    例3-6 コア・ファイルでの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[]
    :
    

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

3.4.3 ファイナライズを保留中のオブジェクトのモニター

OutOfMemoryError例外が「Java heap space」という詳細メッセージとともにスローされた場合、その原因はファイナライザの過度の使用によるものである可能性があります。 これを診断するために、ファイナライズを保留しているオブジェクトの数をモニターするためのオプションが複数用意されています。

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

  • Oracle SolarisおよびLinuxオペレーティング・システムでは、jmapユーティリティを-finalizerinfoオプションとともに使用することで、ファイナライズを待機しているオブジェクトに関する情報を出力できます。

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

ファイナライズの検出および移行の詳細は、「Java Platform, Standard Edition HotSpot仮想マシン・ガベージ・コレクション・チューニング・ガイド」「ファイナライズおよび弱い参照、ソフト参照およびファントム参照」を参照してください。

目次      

Copyright © 1993, 2025, Oracle and/or its affiliates. All rights reserved.