【问题标题】:Java thread leaks when calling back from native thread via JNI通过 JNI 从本机线程回调时 Java 线程泄漏
【发布时间】:2013-12-18 00:19:07
【问题描述】:

总结:当从本地创建的线程上的本地代码回调 Java 时,我看到 Java 线程泄漏。

(2014 年 2 月 11 日更新:我们将此作为对 Oracle 的支持请求。现在,Oracle 在 Java 7 更新 45 上已将其提交为 confirmed。它仅影响 64 位 Linux(可能还有 Mac)平台:32- bit Linux 不受影响)。

(2014 年 4 月 29 日更新:Oracle 对此问题进行了修复,并将在 Java 7 更新 80 中发布)。

我有一个由 Java 层和本机库组成的应用程序。 Java 层通过 JNI 调用本机库:这会导致新的本机线程开始运行,该线程会回调 Java。因为新的本机线程没有附加到 JVM,所以需要在执行回调之前附加它,然后再分离。执行此操作的常用方法似乎是将调用回 Java 的代码与 AttachCurrentThread / DetachCurrentThread 调用括起来。这很好用,但是对于我们的应用程序(它非常频繁地回调到 Java)来说,每次附加和分离的开销都很大。

有几个地方描述的优化(如herehere)建议使用基于线程本地存储的机制来消除这个问题:基本上每次触发本机回调时,都会测试线程以查看如果它已经附加到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 中。

【问题讨论】:

  • 有点奇怪,所有本机句柄都相同 140503373506304。
  • 这个问题似乎仅限于 64 位 Linux(在 RHEL 和克隆上看到)。
  • Oracle 现已确认该问题:更新了带有链接的描述。还没有修复...
  • 提出问题,识别为错误。我怎样才能给你更多的声誉? :)
  • @MalcolmWilkins 你应该自己回答这个问题,确认它是 JVM 中的一个错误以及它在哪个版本中被修复。

标签: java c++ multithreading callback java-native-interface


【解决方案1】:

Oracle 已通过 JVM 修复了此问题,并将在 Java 7 更新 80 中发布。

好吧,如果您不接受自己的答案,也许您会接受这个答案。 至少它不会再为零答案问题吸引那么多的流量了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-30
    • 1970-01-01
    • 2016-12-09
    • 2012-02-17
    • 2020-04-06
    • 1970-01-01
    • 2022-10-05
    • 1970-01-01
    相关资源
    最近更新 更多