一部の機能にネイティブ ライブラリを使用する Java アプリケーションがあります。JNI を使用してネイティブ ライブラリを制御し、ライブラリから非同期コールバックも受け取ります。これは、相互に通信する Java フロントエンドとネイティブ バックエンドと考えることができます。
私はメモリリークに直面しています。アプリケーションを起動して間もなく、メモリはゆっくりと、しかし着実に増加します。というわけで、漏れの原因を調べてみました。
まず、Java フロントエンドを単純な C++ テキスト インターフェイスに置き換えてみました。そうすれば、アプリケーションは Java をまったく使用せず、リークは停止します。したがって、問題は Java フロントエンドにあるはずです。
そこで、ヒープが増加するかどうかを確認するために jvisualVM を起動しましたが、そうではないことがわかりました。Java ヒープ サイズはほぼ一定でした。私も xmx32m でプログラムを起動しましたが、メモリはOutOfMemoryError
s なしで 100m をはるかに超えて増加し続けました。実際、jvisualVM は約 7m の Java ヒープを示しました。
そこで、WinDbg を使用してプログラムをさらに掘り下げました。!heap -s
コマンドでヒープパターンを分析したところ、次のようになりました。
新しく実行されたプログラムをヒープします。
0:059> !heap -s
LFH Key : 0x382288b9
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
00330000 00000002 2048 1704 2048 22 71 2 0 0 LFH
005b0000 00001002 1088 212 1088 68 3 2 0 0 LFH
00aa0000 00001002 1088 108 1088 15 7 2 0 0 LFH
004f0000 00001002 15424 12876 15424 1372 89 9 0 1 LFH
...
0:059> !heap -stat -h 004f0000
heap @ 004f0000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
2b110 20 - 562200 (60.36)
98 166e - d5150 (9.33)
6cd20 1 - 6cd20 (4.77)
...
約30分間実行されているプログラムをヒープします。
0:046> !heap -s
LFH Key : 0x5e47ba72
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
006b0000 00000002 2048 1744 2048 46 92 2 0 0 LFH
00200000 00001002 1088 220 1088 68 3 2 0 0 LFH
00950000 00001002 1088 108 1088 15 7 2 0 0 LFH
001b0000 00001002 47808 31936 47808 1855 102 12 0 0 LFH
...
0:046> !heap -stat -h 001b0000
heap @ 001b0000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
98 59d1 - 355418 (36.67)
2b110 10 - 2b1100 (29.61)
6cd20 1 - 6cd20 (4.68)
...
これで、サイズ 98 のブロック数の増加によってリークが発生していることがはっきりとわかります。しかし、ブロックの 1 つを で分析しようとすると!heap -p -a
、次のようになります。
*** エラー: シンボル ファイルが見つかりませんでした。jvm.dll のシンボルをエクスポートするようにデフォルト設定
スタックトレースなし。したがって、ブロックは jvm.dll 内のどこかに割り当てられます。また、JVM 用の pdb がないため、リークをこれ以上デバッグすることはできません。
コード内でリークが発生している場所を特定できました。Java フロントエンドへのすべてのコールバックは、1 つの関数を通過します。
void callback(JNIEnv *env, int stream, double value, char *callbackName){
jclass jni = env->FindClass("nativ/Callbacks");
jmethodID callbackMethodID = env->GetStaticMethodID(jni, callbackName, "(ID)V");
jvalue params[2];
params[0].i = (long)(stream);
params[1].d = value;
env->CallStaticVoidMethodA(jni, callbackMethodID, params); //commenting this out stops the leaks
}
最後のコマンドをコメントアウトすると、リークは止まりますが、フロントエンドにフィードバックが返されません。
これは JVM のバグでしょうか? どうすればわかりますか?