当前位置: 首页 > 知识库问答 >
问题:

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

越英范
2023-03-14

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

(2014年2月11日更新:我们向Oracle提出了支持请求。Oracle已在Java 7 Update 45上确认了这一点。它只影响64位Linux(可能还有Mac)平台:32位Linux不受影响)。

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

我有一个由Java层和本机库组成的应用程序。Java层通过JNI调用本机库:这会导致一个新的本机线程开始运行,该线程会调用回Java。因为新的本机线程没有连接到JVM,所以需要在回调之前连接它,然后再分离。通常的方法似乎是用AttachCurrentThread/DetachCurrentThread调用将调用回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版本也存在问题。

共有1个答案

龙俊美
2023-03-14

Oracle已经用JVM修复了这个问题,它将在Java7更新80中发布。

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

 类似资料:
  • 我有一个Android应用程序,它由一些本机线程(未连接到JVM)组成,需要能够调用Java对象的方法。 我打算这样做的方式是创建一个JNI函数,我从相关的Java对象调用它,它允许我在静态本机数据结构中获取和缓存所需的java对象方法ID、JNIEnv和对象引用,以便我的本机线程可以(线程安全地)访问所需的方法(例如,使用(*env)- 我不相信这种方法会起作用,因为我读到JNIEnv指针不能在

  • 我有一个通过JNI调用C++共享对象的Java对象。在C++中,我保存了对JNIEnv和JObject的引用。 我还有一个GLSurface呈现器,它最终在一个不同的线程glthread上调用上面提到的C++共享对象。然后,我试图使用我最初保存的jobject回调到我最初的Java对象,但我想,因为我在GLThread上,我得到了以下错误。 回Java的代码: } 如果我使用env->newobj

  • 在android中,我使用pthread_create来创建一个本地线程,然后在回调过程中,调用FindClass来获取一个Java类。但是它不起作用。我从android jni提示中获得提示,我从Android JNI中的任何线程中找到了FindClass中的解决方案 我为我的项目修改它,如下[编辑] 程序在env退出- 如果我删除

  • 问题内容: 我有一个从C调用的Java函数的JNI包装器。我试图从不同的线程调用某些方法,并且在尝试获取JNIEnv指针的新副本时收到错误消息。下面使用m并在每种方法中调用它: 从主线程/初始线程调用JVM时,该JVM已被实例化,并且该(和其他方法)运行。当我获得envRes的值时,在子线程中它保持-2。 问题答案: 请参阅本章的文档。 在使用任何JNI函数之前,您至少需要为每个本机线程调用一次。

  • 问题内容: 绿色线程和本机线程有什么区别? 为什么将其命名为绿色和原生? 我是编程世界的新手。我喜欢学习Java。在经历Java线程面试问题时,我发现了这一点。我听说过线程,但是没有听说过这些绿色线程和本地线程。我对绿色线程和本机线程感到困惑,但不清楚。 在这种情况下,线程被称为绿色线程还是本地线程?(我的意思是在编程中) 问题答案: 绿色线程和本机线程有什么区别? 绿色线程由虚拟机调度。 本机线

  • 问题内容: 我有一个方法。值在内部被更改,我想将其返回给该方法。有没有办法做到这一点? 问题答案: 可以使用局部最终变量数组。该变量必须是非基本类型,因此可以使用数组。你还需要同步两个线程,例如使用CountDownLatch: 你也可以这样使用an Executor和a Callable: