2013-03-28 40 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