1

一部の機能にネイティブ ライブラリを使用する Java アプリケーションがあります。JNI を使用してネイティブ ライブラリを制御し、ライブラリから非同期コールバックも受け取ります。これは、相互に通信する Java フロントエンドとネイティブ バックエンドと考えることができます。

私はメモリリークに直面しています。アプリケーションを起動して間もなく、メモリはゆっくりと、しかし着実に増加します。というわけで、漏れの原因を調べてみました。

まず、Java フロントエンドを単純な C++ テキスト インターフェイスに置き換えてみました。そうすれば、アプリケーションは Java をまったく使用せず、リークは停止します。したがって、問題は Java フロントエンドにあるはずです。

そこで、ヒープが増加するかどうかを確認するために jvisualVM を起動しましたが、そうではないことがわかりました。Java ヒープ サイズはほぼ一定でした。私も xmx32m でプログラムを起動しましたが、メモリはOutOfMemoryErrors なしで 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 のバグでしょうか? どうすればわかりますか?

4

1 に答える 1

0

malloc() は内部的に HeapAlloc() を呼び出します。ライブラリがJVMの内部状態への参照を保持している限り、JVMによって割り当てられたメモリを解放するには「リリース」メソッドが必要だと思います。

于 2013-05-05T05:25:57.033 に答える