2013-03-28 69 views
3

我從C代碼調用Java方法。每次撥打電話時,我都會調用AttachCurrentThread,並在呼叫完成後調用DetachCurrentThread。爲什麼調用DetachCurrentThread()會導致垃圾收集過多?

這工作正常,但問題是,我看到由該即幾乎每個通過JNI調用引起的後續垃圾收集。 VisualVM上的小圖集合基本上都是綠色的!從本地代碼到Java的調用速率是每秒數百次。在那些調用期間,我還可以看到過多的Java線程被創建,如Thread-34543,Thread-34544,Thread-34545等,這可能是GC的原因。看起來每個通話都是通過不同的線程完成的。

任何人都看到了?

只是添加到,當我不DetachCurrentThread根本沒有GC,但VisualVM中的線程視圖顯示數百個線程連接到虛擬機。有小費嗎?

JVM設置

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

平臺: Ubuntu的12.04 的Linux 3.2.0-35泛型#55-Ubuntu的SMP星期三12月05日17點42分16秒UTC 2012 x86_64 x86_64 x86_64 GNU/Linux

Java:

OpenJDK的運行時環境(1.11.5 IcedTea6)(6b24-1.11.5-0ubuntu1〜12.04.1) OpenJDK的64位服務器VM(構建20.0-B12,混合模式)

UPDATE 2013年3月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中創建過多的線程abject。 最後運行顯示

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 

,其中第一列是連接線程不具有與它相關聯的有效ENV電話號碼。 任何想法爲什麼會發生?

回答

5

AttachCurrentThread函數將您的當前本機線程附加到JVM Thread對象。這是因爲JVM中的所有操作都發生在線程的上下文中(在JNIEnv對象的C端引用)。

如果你的C代碼不是多線程的,你不需要調用attach/detach;只需使用從JNI_CreateJavaVM獲得的JNIEnv即可。如果您的C線程數有限,則可以在本機線程啓動時調用attach,並在線程的使用期限內繼續使用相同的JNIEnv(但您必須附加每個 C線程)。如果你正在創建大量的C線程,那麼你沒有選擇:你必須附加每個線程。

我懷疑是因爲JVM使用線程本地分配塊而發生「過多」的垃圾收集:每個Java線程都有一個保留區域的Eden內存用於分配(以防止與其他線程爭用)。當本地線程被分離時,該TLA有資格收集(並且,根據TLA的大小,你可能只是因爲短暫的附加而填充了Eden)。您可能可以通過-XX:-UseTLAB禁用此行爲,但這可能會導致比解決問題更多的問題(因爲JVM必須在每次分配時鎖定其內部狀態)。

TLDR:如果您不創建本地線程,則不需要經常附加/分離。


編輯響應於評論

我建議緩存JNIEnv指針,和附接/拆卸的需要的基礎上。假設您使用PThreads,則可以使用pthread_setspecific將環境指針與當前本機線程相關聯。如果您的代碼是從沒有環境指針的線程調用的,則調用AttachCurrentThread並將結果與​​線程一起存儲。

當你這樣做時,當本地線程即將死亡時,你還需要使用thread cleanup handler來調用DetachCurrentThread。假設你正在使用的庫不會對清理棧做任何愚蠢的處理,這應該可以防止java對象泄漏。

+0

解釋一下。在我的情況下,本地線程創建在我的JNI庫之外,所以我無法控制這些線程的數量。我注意到,當沒有太多活動時,只有4-5個原生線程正在使用。只有在重載線程數量增加的情況下。奇怪的是,虛擬機不會以任何方式嘗試重用JVM線程對象。另外我注意到,在我的情況下,整個應用程序工作更有效率,當我不分離。很顯然,我需要早晚分離,所以我正在考慮某種本地線程管理器,它會分離未曾調用過VM代碼的線程一段時間。 – ivenhov

+0

謝謝parsifal。請參閱我的更新瞭解我所觀察到的更多細節。感謝有關清理處理程序和setspecific的提示 – ivenhov