Подтвердить что ты не робот

Потоки Java-потоков при обратном вызове из собственного потока через JNI

Резюме. Я вижу утечку Java-потоков при обращении к Java из собственного кода в изначально созданном потоке.

(Обновление 11 февраля 2014 года. Мы подняли это как запрос поддержки с Oracle. Теперь он подтвержден Oracle на основе обновления для Java 7 45. Он затрагивает только 64 -битные Linux (и, возможно, Mac) платформы: 32-разрядная Linux не затронута).

(Обновление 29 апреля 2014 года: у Oracle есть исправление для этой проблемы, и оно будет выпущено в версии 7 на Java 7).

У меня есть приложение, состоящее из уровня Java и собственной библиотеки. Слой Java вызывает в родную библиотеку через JNI: это приводит к запуску нового собственного потока, который обращается к Java. Поскольку новый родной поток не привязан к JVM, его необходимо привязать перед выполнением обратного вызова, а затем отсоединить его потом. Обычный способ сделать это, похоже, состоит в том, чтобы скопировать код, который обращается к Java с помощью AttachCurrentThread/DetachCurrentThread вызовов. Это прекрасно работает, но для нашего приложения (которое очень часто обращается к 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.

4b9b3361

Ответ 1

Oracle устранила эту проблему с помощью JVM, и она будет выпущена в обновлении версии 7 на Java.

Хорошо, если вы не собираетесь принимать свой собственный ответ, возможно, вы примете это. По крайней мере, он не будет тянуть столько трафика для вопроса с нулевыми ответами.