Java线程通过JNI从本地线程callback时泄漏
简介:当从本地创build的线程上的本机代码调用回Java时,我看到Java线程泄漏。
(更新2014年2月11日:我们提出了这个问题作为Oracle的支持请求,现在已经被Oracle 确认在Java 7更新45上。它只影响64位Linux(也可能是Mac)平台:32位Linux不受影响) 。
(2014年4月29日更新:甲骨文已经解决了这个问题,并将在Java 7更新80中发布)。
我有一个由Java层和本地库组成的应用程序。 Java层通过JNI调用本地库:然后这会导致一个新的本地线程开始运行,这将调用回Java。 因为新的本地线程没有连接到JVM,所以在进行callback之前需要先连接,然后再分离。 通常的做法似乎是用AttachCurrentThread / DetachCurrentThread调用来callbackJava的代码。 这个工作正常,但是对于我们的应用程序(这个应用程序非常频繁地调用Java),每次附加和分离的开销都很大。
在几个地方(像这里和这里 )描述的优化build议使用基于线程本地存储的机制来消除这个问题:基本上每次触发本地callback时,testing线程是否已经连接到JVM:如果没有,则将其附加到JVM,并使用线程本地存储机制在线程退出时自动分离该线程。 我已经实现了这一点,但是虽然附件和分离似乎正确地发生,这导致在Java端的线程泄漏。 我相信我正在做的一切正在努力,看看可能是错的。 有一段时间我一直在抨击我的头脑,我会非常感激任何见解。
我用缩减的forms重新创build了这个问题。 以下是本地图层的代码。 我们这里有一个封装器,它封装了返回当前线程的JNIEnv指针的过程,使用POSIX线程本地存储机制自动分离线程,如果它尚未连接。 有一个callback类用作Javacallback方法的代理。 (为了消除创build和删除与Java对象相关的全局对象引用的额外复杂性,我已经使用了对静态Java方法的callback,这与这个问题无关)。 最后有一个JNI方法,当被调用时,构造一个callback,并创build一个新的本地线程,并等待它完成。 这个新创build的线程调用一次callback然后退出。
#include <jni.h> #include <iostream> #include <pthread.h> using namespace std; /// Class to automatically handle getting thread-specific JNIEnv instance, /// and detaching it when no longer required class JEnvWrapper { public: static JEnvWrapper &getInstance() { static JEnvWrapper wrapper; return wrapper; } JNIEnv* getEnv(JavaVM *jvm) { JNIEnv *env = 0; jint result = jvm->GetEnv((void **) &env, JNI_VERSION_1_6); if (result != JNI_OK) { result = jvm->AttachCurrentThread((void **) &env, NULL); if (result != JNI_OK) { cout << "Failed to attach current thread " << pthread_self() << endl; } else { cout << "Successfully attached native thread " << pthread_self() << endl; } // ...and register for detach when thread exits int result = pthread_setspecific(key, (void *) env); if (result != 0) { cout << "Problem registering for detach" << endl; } else { cout << "Successfully registered for detach" << endl; } } return env; } private: JEnvWrapper() { // Initialize the key pthread_once(&key_once, make_key); } static void make_key() { pthread_key_create(&key, detachThread); } static void detachThread(void *p) { if (p != 0) { JavaVM *jvm = 0; JNIEnv *env = (JNIEnv *) p; env->GetJavaVM(&jvm); jint result = jvm->DetachCurrentThread(); if (result != JNI_OK) { cout << "Failed to detach current thread " << pthread_self() << endl; } else { cout << "Successfully detached native thread " << pthread_self() << endl; } } } static pthread_key_t key; static pthread_once_t key_once; }; pthread_key_t JEnvWrapper::key; pthread_once_t JEnvWrapper::key_once = PTHREAD_ONCE_INIT; class Callback { public: Callback(JNIEnv *env, jobject callback_object) { cout << "Constructing callback" << endl; const char *method_name = "javaCallback"; const char *method_sig = "(J)V"; env->GetJavaVM(&m_jvm); m_callback_class = env->GetObjectClass(callback_object); m_methodID = env->GetStaticMethodID(m_callback_class, method_name, method_sig); if (m_methodID == 0) { cout << "Couldn't get method id" << endl; } } ~Callback() { cout << "Deleting callback" << endl; } void callback() { JNIEnv *env = JEnvWrapper::getInstance().getEnv(m_jvm); env->CallStaticVoidMethod(m_callback_class, m_methodID, (jlong) pthread_self()); } private: jclass m_callback_class; jmethodID m_methodID; JavaVM *m_jvm; }; void *do_callback(void *p) { Callback *callback = (Callback *) p; callback->callback(); pthread_exit(NULL); } extern "C" { JNIEXPORT void JNICALL Java_com_test_callback_CallbackTest_CallbackMultiThread(JNIEnv *env, jobject obj) { Callback callback(env, obj); pthread_t thread; pthread_attr_t attr; void *status; int rc; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); rc = pthread_create(&thread, &attr, do_callback, (void *) &callback); pthread_attr_destroy(&attr); if (rc) { cout << "Error creating thread: " << rc << endl; } else { rc = pthread_join(thread, &status); if (rc) { cout << "Error returning from join " << rc << endl; } } }
Java代码非常简单:它只是在循环中反复调用本地方法:
package com.test.callback; public class CallbackTest { static { System.loadLibrary("Native"); } public void runTest_MultiThreaded(int trials) { for (int trial = 0; trial < trials; trial++) { // Call back from this thread CallbackMultiThread(); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } static void javaCallback(long nativeThread) { System.out.println("Java callback: native thread: " + nativeThread + ", java thread: " + Thread.currentThread().getName() + ", " + Thread.activeCount() + " active threads"); } native void CallbackMultiThread(); }
以下是该testing的一些示例输出:您可以看到,虽然本机层报告本机线程已成功挂接和分离,但每次触发callback时都会创build一个新的Java线程:
Constructing callback Successfully attached native thread 140503373506304 Successfully registered for detach Java callback: native thread: 140503373506304, java thread: Thread-67, 69 active threads Successfully detached native thread 140503373506304 Deleting callback Constructing callback Successfully attached native thread 140503373506304 Successfully registered for detach Java callback: native thread: 140503373506304, java thread: Thread-68, 70 active threads Successfully detached native thread 140503373506304 Deleting callback Constructing callback Successfully attached native thread 140503373506304 Successfully registered for detach Java callback: native thread: 140503373506304, java thread: Thread-69, 71 active threads Successfully detached native thread 140503373506304 Deleting callback Constructing callback Successfully attached native thread 140503373506304 Successfully registered for detach Java callback: native thread: 140503373506304, java thread: Thread-70, 72 active threads Successfully detached native thread 140503373506304 Deleting callback Constructing callback Successfully attached native thread 140503373506304 Successfully registered for detach Java callback: native thread: 140503373506304, java thread: Thread-71, 73 active threads Successfully detached native thread 140503373506304 Deleting callback
只是补充一点:我使用的开发平台是CentOS 6.3(64位)。 Java版本是Oracle发行版本1.7.0_45,虽然OpenJDK发行版本1.7和1.6也显示了这个问题。
Oracle已经解决了JVM的这个问题,并将在Java 7更新80中发布。
那么如果你不接受你自己的答案,也许你会接受这个。 至less它不会再为零答案提出问题了。