简体   繁体   English

从本机线程通过JNI回调时,Java线程泄漏

[英]Java thread leaks when calling back from native thread via JNI

Summary: I am seeing Java thread leaks when calling back into Java from native code on a natively-created thread. 简介:在本机创建的线程上从本机代码调用Java时,我看到Java线程泄漏。

(Update 11 Feb 2014: We raised this as a support request with Oracle. It has now been confirmed by Oracle on Java 7 update 45. It only affects 64-bit Linux (and possibly Mac) platforms: 32-bit Linux is unaffected). (2014年2月11日更新:我们将此作为对Oracle的支持请求提出。现在Oracle已经在Java 7更新45上证实了它。它只影响64位Linux(可能还有Mac)平台:32位Linux不受影响) 。

(Update 29 April 2014: Oracle have a fix for this issue, and it will be released in Java 7 update 80). (2014年4月29日更新:Oracle已解决此问题,并将在Java 7更新80中发布)。

I have an application consisting of a Java layer and a native library. 我有一个由Java层和本机库组成的应用程序。 The Java layer calls into the native library via JNI: this then causes a new native thread to start running, which calls back into Java. Java层通过JNI调用本机库:这会导致新的本机线程开始运行,后者将调用回Java。 Because the new native thread is not attached to the JVM, it needs to be attached prior to doing the callback, then detached afterwards. 由于新的本机线程未附加到JVM,因此需要在执行回调之前将其附加,然后再分离。 The usual way to do this seems to be to bracket the code that calls back into Java with AttachCurrentThread / DetachCurrentThread calls. 通常的方法是使用AttachCurrentThread / DetachCurrentThread调用将回调到Java的代码括起来。 This works fine, but for our application (which calls back into Java very frequently) the overhead of attaching and detaching every time is significant. 这样可以正常工作,但对于我们的应用程序(非常频繁地调用Java),每次附加和分离的开销都很大。

There is an optimization described in several places (like here and here ) that recommends using mechanisms based on thread local storage to eliminate this problem: essentially every time the native callback is triggered, the thread is tested to see if it is already attached to the JVM: if not, it is attached to the JVM and the thread local storage mechanism is used to automatically detach the thread when it exits. 在几个地方(比如这里这里 )描述了一个优化,它建议使用基于线程本地存储的机制来消除这个问题:基本上每次触发本机回调时,都会测试线程是否已经附加到JVM:如果没有,它将附加到JVM,并且线程本地存储机制用于在线程退出时自动分离线程。 I have implemented this, but although the attach and detach appears to be occurring correctly, this causes a leak of threads on the Java side. 我已经实现了这一点,但是虽然附加和分离似乎正确发生,但这会导致Java端的线程泄漏。 I believe I am doing everything correctly and am struggling to see what might be wrong. 我相信我正在做正确的事情,并且正在努力寻找可能出错的地方。 I have been bashing my head on this for a while now and I would be very grateful for any insights. 我一直在抨击这个问题一段时间,我会非常感谢任何见解。

I have recreated the problem in cut-down form. 我已经以减少的形式重新创建了问题。 Below is the code for the native layer. 下面是本机层的代码。 What we have here is a wrapper that encapsulates the process of returning a JNIEnv pointer for the current thread, using the POSIX thread-local storage mechanism to automatically detach the thread if it wasn't already attached. 我们这里有一个包装器,它封装了为当前线程返回JNIEnv指针的过程,使用POSIX线程本地存储机制自动分离线程(如果它尚未连接)。 There is a callback class that acts as a proxy for the Java callback method. 有一个回调类充当Java回调方法的代理。 (I have used callback to a static Java method in order to eliminate the extra complication of creating and deleting global object references to the Java object, which are irrelevant to this problem). (我已经使用回调到静态Java方法,以消除创建和删除对Java对象的全局对象引用的额外复杂性,这与此问题无关)。 Finally there is a JNI method that when called, constructs a callback, and creates a new native thread and waits for it to complete. 最后有一个JNI方法,在调用时,构造一个回调,并创建一个新的本机线程并等待它完成。 This newly created thread calls the callback once then exits. 这个新创建的线程调用回调一次然后退出。

#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;
        }
    }
}

The Java code is very simple: it just repeatedly calls the native method in a loop: 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();  
}

Below is some sample output from this test: you can see that although the native layer is reporting that the native thread is successfully being attached and detached, every time the callback is triggered a new Java thread is created: 下面是此测试的一些示例输出:您可以看到虽然本机层报告本机线程已成功连接和分离,但每次触发回调时都会创建一个新的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

Just to add: the development platform I am using is CentOS 6.3 (64-bit). 只是补充一点:我使用的开发平台是CentOS 6.3(64位)。 The Java version is the Oracle distribution version 1.7.0_45, although the problem also shows with the OpenJDK distribution, versions 1.7 and 1.6. Java版本是Oracle发行版本1.7.0_45,尽管问题也出现在OpenJDK发行版1.7和1.6版本中。

Oracle have fixed this issue with the JVM, and it will be released in Java 7 update 80. Oracle已经使用JVM解决了这个问题,它将在Java 7更新80中发布。

Well if you aren't going to accept your own answer maybe you'll accept this one. 好吧,如果你不接受自己的答案,也许你会接受这个。 At least it won't be pulling in as much traffic for a zero answers question any more. 至少它不会为零回答问题吸引尽可能多的流量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM