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

3.5 ネイティブ・コードでのリークの診断

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

3.5.1 すべてのメモリー割当ておよび解放呼出しの追跡

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

IBM Rational PurifyのようなツールやSun Studioのdbxデバッガの実行時チェック機能を使用すると、ネイティブ・コードの通常の状況でこれらのリークを検出したり、初期化されていないメモリーへの割当てや解放されたメモリーへのアクセスを表すネイティブ・ヒープ・メモリーへのアクセスを検出したりできます。「dbxデバッガによるリークの検出」を参照してください。

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

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

3.5.2 JNIライブラリのすべてのメモリー割当ての追跡

JNIライブラリを記述する場合は、簡単なラッパー・アプローチを使用して、ライブラリでメモリー・リークが発生しないことを保証する局所的な方法の作成を検討してください。

例3-7の手順は、JNIライブラリ用の簡単な局所的割当て追跡方法です。まず、すべてのソース・ファイルに次の行を定義します。

これにより、例3-8の関数を使用してリークを監視できます。

その場合、JNIライブラリは定期的(またはシャットダウン時)にtotal_allocated変数の値をチェックして、それが妥当であることを確認する必要があります。前述のコードを拡張して、残された割当てをリンク・リストに保存し、リークしたメモリーがどこで割り当てられたかを報告することもできます。これは、単一セットのソース内のメモリー割り当てを追跡するための、局所的で移植可能な方法です。debug_free()debug_malloc()に由来するポインタのみを指定して呼び出されたことを確認する必要があり、realloc()calloc()strdup()などが使用されていた場合は、これらに対しても同じような関数を作成する必要があります。

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

3.5.3 オペレーティング・システム・サポートによるメモリー割当ての追跡

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

  • Windowsでは、MSDNライブラリでデバッグ・サポートを検索してください。Microsoft C++コンパイラには、メモリー割当てを追跡するための追加サポートを自動的に取り込む、/Mdおよび/Mddコンパイラ・オプションがあります。

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

  • Oracle Solarisオペレーティング・システムには、watchmallocツールが用意されています。Oracle Solaris 9オペレーティング・システムupdate 3ではlibumemツールも導入されました(「libumemツールによるリークの検出」を参照)。

3.5.4 dbxデバッガによるリークの検出

dbxデバッガには、リークを検出できる実行時チェック(RTC)機能が含まれています。dbxデバッガはOracle Solaris Studioの一部で、Linuxでも使用できます。

例3-9に、dbxセッションのサンプルを示します。

例3-9 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デバッガが実際にはリークではないメモリー・リークを報告することがあります。

注意: 例3-9では、仮想マシンlibjvm.soとJavaサポート・ライブラリlibjava.soで報告されたリークを抑制するために、2つのsuppressコマンドを使用しています。

3.5.5 libumemツールによるリークの検出

libumem.soライブラリとモジュラ・デバッガmdbは、最初にOracle Solaris 9オペレーティング・システムupdate 3で導入され、メモリー・リークをデバッグするために使用できます。libumemを使用する前に、例3-10に示すように、libumemライブラリをプリロードし、環境変数を設定する必要があります。

ここで、Javaアプリケーションを実行しますが、それを終了する前に停止します。例3-11では、_exitシステム・コールが呼び出されたときにtrussを使用してプロセスを停止しています。

この時点で、例3-12に示すように、mdbデバッガを接続できます。

::findleaksコマンドは、mdbでメモリー・リークを検出するコマンドです。リークが検出されたら、このコマンドによって割当て呼出しのアドレス、バッファ・アドレス、および最も近いシンボルを出力します。

bufctl構造体をダンプして、メモリー・リークが発生した割当てのスタック・トレースを取得することもできます。この構造体のアドレスは、::findleaksコマンドの出力から取得できます。

メモリー・リークの原因のトラブルシューティングについては、libumemを使用したメモリー・リークの分析を参照してください。

目次      

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