ProgramingTip

JNI를 통해 원시 코드에서 다시 호출 할 때 Java 누수

bestdevel 2021. 1. 8. 23:03
반응형

JNI를 통해 원시 코드에서 다시 호출 할 때 Java 누수


요약 : 기본적으로 생성 된 기본 코드에서 Java로 다시 호출 할 때 자바 누수가 발생합니다.

(2014 년 2 월 11 일 업데이트 문제 : Oracle의 지원 요청으로이를 제기했습니다. 이제 Java 7 45에서 Oracle에 의해 확인 되었습니다. 64 비트 Linux (및 Mac) 플랫폼에 영향을 미쳤습니다. 32 비트 Linux는 영향을 미쳤습니다. 발생하지 않음).

(2014 년 4 월 29 일 업데이트 : Oracle 은이 문제를 수정하고 Java 7 업데이트 80에서 릴리스 될 예정입니다.)

Java 레이어와 외장 라이브러리로 구성된 애플리케이션이 있습니다. Java 계층은 JNI를 통해 원시 라이브러리를 호출합니다. 그러면 새 원시 코드가 실행되기 시작하여 Java로 다시 호출됩니다. 새 원시적 인 경우는 JVM에 연결되지 않기 때문에 예방을 수행하기 전에 연결해야합니다. 이를 수행하는 일반적인 방법은 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에서도 문제가 나타납니다.


Oracle은 JVM에서이 문제를 해결했으며 Java 7 업데이트 80에서 릴리스 될 예정입니다.

자신의 대답을 받아들이지 않는다면 아마도이 대답을 받아 들일 것입니다. 적어도 답이없는 질문에 대해 더 이상 많은 트래픽을 끌어들이지는 않을 것입니다.

참조 URL : https://stackoverflow.com/questions/20325792/java-thread-leaks-when-calling-back-from-native-thread-via-jni

반응형