3

C コードから Java メソッドを呼び出しています。呼び出しが行われるたびに、AttachCurrentThread を呼び出し、呼び出しが行われた後に DetachCurrentThread を呼び出します。

これは正常に動作しますが、問題は、JNI を介したほぼすべての呼び出しによって引き起こされる過剰なガベージ コレクションが表示されることです。マイナー コレクションの VisualVM グラフは、基本的にすべて緑色です。ネイティブ コードから Java への呼び出し速度は、毎秒数百です。その呼び出し中に、おそらく GC の理由である Thread-34543、Thread-34544、Thread-34545 などのように、過剰な数の Java スレッドが作成されていることもわかります。各呼び出しは異なるスレッドを介して行われるようです。

誰もこれを見たことがありますか?

それに加えて、DetachCurrentThread を使用しない場合、GC はまったくありませんが、VisualVM のスレッド ビューには VM に接続された数百のスレッドが表示されます。任意のヒント?

JVM 設定

-Xms2048m -Xmx2048m -XX:MaxDirectMemorySize=256M -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom. sun.management.jmxremote.port=3333

プラットフォーム: Ubuntu 12.04 Linux 3.2.0-35-generic #55-Ubuntu SMP Wed Dec 5 17:42:16 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

ジャワ:

OpenJDK ランタイム環境 (IcedTea6 1.11.5) (6b24-1.11.5-0ubuntu1~12.04.1) OpenJDK 64 ビット サーバー VM (ビルド 20.0-b12、混合モード)

更新 2013-03-30

私の問題は別の場所にあると思います。スレッドの ID を出力しましたが、私の JNI コードを呼び出しているスレッドはほとんどないようです。最後の実行では、13 のスレッドが表示されました。問題は、実行時に

if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) == JNI_OK)
    return env;
else
    return NULL;

現在のスレッドに関連付けられている JNIEnv* を取得するには、エラー コード -2 (JNI_EDETACHED) が表示されます。明確にするために、私は DetachCurrentThread をまったく呼び出しません。これらのスレッドがネイティブ ライブラリに戻ってくることを期待しているためです。その場合、ネイティブ スレッドを再度アタッチすると、JVM で過剰な Thread オブジェクトが作成される可能性があります。最終実行ショー

 29 [478e](get_env) Thread 2633996032 has env: (nil), err was: -2
 47 [478e](get_env) Thread 2642388736 has env: (nil), err was: -2
 32 [478e](get_env) Thread 2650781440 has env: (nil), err was: -2
 31 [478e](get_env) Thread 2659174144 has env: (nil), err was: -2
 37 [478e](get_env) Thread 2667566848 has env: (nil), err was: -2
 30 [478e](get_env) Thread 2675959552 has env: (nil), err was: -2
 32 [478e](get_env) Thread 2684352256 has env: (nil), err was: -2
 33 [478e](get_env) Thread 2760873728 has env: (nil), err was: -2
 33 [478e](get_env) Thread 2769266432 has env: (nil), err was: -2
 37 [478e](get_env) Thread 2777659136 has env: (nil), err was: -2
 36 [478e](get_env) Thread 2786051840 has env: (nil), err was: -2
 31 [478e](get_env) Thread 2794444544 has env: (nil), err was: -2
 52 [478e](get_env) Thread 3707176704 has env: (nil), err was: -2

ここで、最初の列は、接続されたスレッドに有効な環境が関連付けられていない呼び出しの数です。なぜそれが起こっているのですか?

4

1 に答える 1

6

この関数は、現在のネイティブ スレッドを JVMオブジェクトにAttachCurrentThreadアタッチします。これが必要なのは、JVM 内のすべての操作がスレッドのコンテキストで発生するためです (オブジェクトの C 側で参照されます)。ThreadJNIEnv

C コードがマルチスレッド化されていない場合、アタッチ/デタッチを呼び出す必要はありません。JNIEnvから取得した を使用するだけですJNI_CreateJavaVM。C スレッドの数が限られている場合は、ネイティブ スレッドの開始時に attach を呼び出し、スレッドJNIEnvの存続期間中同じものを引き続き使用できます (ただし、C スレッドを接続する必要があります)。多数の C スレッドを作成している場合、選択肢はありません。それぞれをアタッチする必要があります。

JVMがスレッドローカル割り当てブロックを使用するため、「過剰な」ガベージコレクションが発生していると思われます。各Javaスレッドには、割り当て用にEdenメモリの予約領域が与えられます(他のスレッドとの競合を防ぐため)。ネイティブ スレッドがデタッチされると、その TLA はコレクションの対象になります (また、TLA の大きさによっては、アタッチが短命であるため、Eden がそれらでいっぱいになる可能性があります)。を使用してこの動作を無効にできる場合がありますが-XX:-UseTLAB、それでは解決できない問題が発生する可能性があります (JVM は割り当てごとに内部状態をロックする必要があるため)。

TLDR : ネイティブ スレッドを作成していない場合は、常にアタッチ/デタッチする必要はありません。


コメントに応じて編集

ポインターをキャッシュし、JNIEnv必要に応じてアタッチ/デタッチすることをお勧めします。PThreads を使用していると仮定すると、pthread_setspecificを使用して環境ポインターを現在のネイティブ スレッドに関連付けることができます。環境ポインターを持たないスレッドからコードが呼び出された場合は、スレッドを呼び出しAttachCurrentThreadて結果を格納します。

これを行うときは、スレッド クリーンアップ ハンドラーDetachCurrentThreadを使用して、ネイティブ スレッドが停止しそうになったときに呼び出す必要もあります。使用しているライブラリがクリーンアップ スタックで愚かなことを何もしないと仮定すると、これにより JavaThreadオブジェクトのリークが防止されるはずです。

于 2013-03-28T17:17:44.570 に答える