複数の手法を使用してネイティブ・コードのメモリー・リークを検出し、切り分けることができます。一般に、すべてのプラットフォームに対応する単独の理想的な解決方法はありません。ネイティブ・コードを診断するためのいくつかの手法を次に示します。
非常によく使われる方法は、ネイティブ割当てのすべての割当て呼び出しと解放呼出しを追跡することです。これは、かなり簡単なプロセスになる場合と、非常に高度なプロセスになる場合があります。ネイティブ・ヒープの割り当てとそのメモリーの使用の追跡をめぐっては、長年にわたって多くの製品が構築されています。
IBM Rational PurifyのようなツールやSun Studioのdbx
デバッガの実行時チェック機能を使用すると、ネイティブ・コードの通常の状況でこれらのリークを検出したり、初期化されていないメモリーへの割当てや解放されたメモリーへのアクセスを表すネイティブ・ヒープ・メモリーへのアクセスを検出したりできます。「dbxデバッガによるリークの検出」を参照してください。
これらのタイプのツールのすべてがネイティブ・コードを使用するJavaアプリケーションで機能するわけではなく、これらのツールは通常はプラットフォーム固有です。仮想マシンはコードを実行時に動的に作成するため、これらのツールはコードを誤って解釈したり、まったく動作しなかったり、誤った情報を与えたりする可能性があります。ツールのベンダーに問い合せて、ツールのバージョンが、使用している仮想マシンのバージョンで動作することを確認してください。
多数の簡単で移植可能なネイティブ・メモリー・リークの検出例は、sourceforgeを参照してください。これらのライブラリやツールのほとんどは、アプリケーションのソースを再コンパイルまたは編集して、割当て関数にラッパー関数を被せることができることを前提としています。より強力なツールでは、これらの割当て関数に動的に介入することで、アプリケーションを変更せずに実行できます。これは、Oracle Solaris 9オペレーティング・システムupdate 3で最初に導入されたlibumem.so
ライブラリのケースです(「libumemツールによるリークの検出」を参照)。
JNIライブラリを記述する場合は、簡単なラッパー・アプローチを使用して、ライブラリでメモリー・リークが発生しないことを保証する局所的な方法の作成を検討してください。
例3-7の手順は、JNIライブラリ用の簡単な局所的割当て追跡方法です。まず、すべてのソース・ファイルに次の行を定義します。
例3-7 ソース・ファイルでのこの手順の定義
#include <stdlib.h> #define malloc(n) debug_malloc(n, __FILE__, __LINE__) #define free(p) debug_free(p, __FILE__, __LINE__)
これにより、例3-8の関数を使用してリークを監視できます。
例3-8 リークを監視するための関数
/* 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
デバッガには、リークを検出できる実行時チェック(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
コマンドを使用しています。
libumem.so
ライブラリとモジュラ・デバッガmdb
は、最初にOracle Solaris 9オペレーティング・システムupdate 3で導入され、メモリー・リークをデバッグするために使用できます。libumem
を使用する前に、例3-10に示すように、libumem
ライブラリをプリロードし、環境変数を設定する必要があります。
例3-10 libumemの環境変数の設定
$ LD_PRELOAD=libumem.so $ export LD_PRELOAD $ UMEM_DEBUG=default $ export UMEM_DEBUG
ここで、Javaアプリケーションを実行しますが、それを終了する前に停止します。例3-11では、_exit
システム・コールが呼び出されたときにtruss
を使用してプロセスを停止しています。
この時点で、例3-12に示すように、mdb
デバッガを接続できます。
::findleaks
コマンドは、mdb
でメモリー・リークを検出するコマンドです。リークが検出されたら、このコマンドによって割当て呼出しのアドレス、バッファ・アドレス、および最も近いシンボルを出力します。
bufctl
構造体をダンプして、メモリー・リークが発生した割当てのスタック・トレースを取得することもできます。この構造体のアドレスは、::findleaks
コマンドの出力から取得できます。
メモリー・リークの原因のトラブルシューティングについては、libumem
を使用したメモリー・リークの分析を参照してください。