通过JNI从本机线程回调时Java线程泄漏
Java thread leaks when calling back from native thread via JNI
摘要:当从本机创建的线程上的本机代码回调到Java时,我看到Java线程泄漏。
(2014年2月11日更新:我们向Oracle提出了这一支持请求。现在,Oracle已在Java 7更新45中确认了这一点。它只影响64位Linux(可能还有Mac)平台:32位Linux不受影响)。
(2014年4月29日更新:Oracle对此问题进行了修复,并将在Java 7更新80中发布)。
我有一个由Java层和本地库组成的应用程序。Java层通过JNI调用本机库:这将导致一个新的本机线程开始运行,该线程调用回Java。因为新的本机线程没有连接到JVM,所以需要在进行回调之前连接它,然后再分离它。通常的方法似乎是用AttachCurrentThread/DeachCurrentThread调用将调用回Java的代码括起来。这很好,但对于我们的应用程序(它经常调用Java)来说,每次附加和分离的开销是很大的。
有几个地方描述了一个优化(比如这里和这里),建议使用基于线程本地存储的机制来消除这个问题:基本上每次触发本机回调时,都会测试线程,看看它是否已经连接到JVM:如果没有,它连接到JVM,线程本地存储机制用于在线程退出时自动分离线程。我已经实现了这一点,但尽管连接和分离看起来是正确的,但这会导致Java端的线程泄漏。我相信我做的每件事都是正确的,正在努力找出可能出了什么问题。我已经在这个问题上思考了一段时间,如果有任何见解,我将不胜感激。
我已经以精简的形式重新创建了这个问题。下面是本机层的代码。我们这里有一个包装器,它封装了返回当前线程的JNIEnv指针的过程,使用POSIX线程本地存储机制在线程尚未连接的情况下自动分离线程。有一个回调类充当Java回调方法的代理。(我使用了对静态Java方法的回调,以消除创建和删除Java对象的全局对象引用的额外复杂性,这与这个问题无关)。最后,还有一个JNI方法,当被调用时,它会构造一个回调,并创建一个新的本地线程并等待它完成。这个新创建的线程调用回调一次,然后退出。
#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();
}
以下是该测试的一些示例输出:您可以看到,尽管本机层报告本机线程已成功连接和分离,但每次触发回调时,都会创建一个新的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中发布。
如果你不愿意接受自己的答案,也许你会接受这个答案。至少它不会再为一个零答案的问题吸引那么多的流量了。
- 如何在JNI中从线程内部调用JAVA方法
- C 多线程JAVA JNI方法调用
- 线程中的异常 "main" java.lang.UnsatisfiedLinkError: no JNTIest in java.library.path
- 通过其他线程通过JNI呼叫保存的Java对象
- 从 Java 线程到 C++
- 线程"main" java.lang.UnsatisfiedLinkError: Native.initiate(I)V 从 Java 运行本机 dll 时
- JNI : 线程"AWT-EventQueue-0" java.lang.UnsatisfiedLinkError 中的异常
- Java:调用本机方法,给出"线程"main"java.lang.UnsatisfiedLinkError中的异常"
- JNI - Java 在本机线程完成执行之前退出
- 通过JNI从本机线程回调时Java线程泄漏
- 与单线程相比,c++/java的多线程性能结果参差不齐
- 线程"main" java.lang.UnsatisfiedLinkError: java.library.path 中没有libopencv_java247
- 在多线程程序中共享资源 C++ 与 Java
- JNI 中的 Java 线程C++仅使用一个内核的环境.Arm 处理器和 Ubuntu
- 在 Python 和 Java 中设置线程亲和力
- Java 中的线程与 C++ 中的线程不同吗?
- Java 和 C++/Qt 中的线程差异
- JNI使用多个线程从C++调用Java
- C++创建线程池(类似于java)
- 线程"main" java.lang.UnsatisfiedLinkError 中的异常