Le thread Java fuit lors du rappel du thread natif via JNI

Résumé: Je vois des fuites de thread Java lors du rappel en Java à partir du code natif sur un thread créé en natif.

(Mise à jour 11 févr. 2014: nous avons soulevé cette question avec Oracle. Elle a maintenant été confirmée par Oracle sur Java 7 mise à jour 45. Elle ne concerne que les plates-formes Linux 64 bits (et éventuellement Mac)) .

(Mise à jour du 29 avril 2014: Oracle a un correctif pour ce problème, qui sera publié sous Java 7, mise à jour 80).

J’ai une application composée d’une couche Java et d’une bibliothèque native. La couche Java appelle la bibliothèque native via JNI: un nouveau thread natif commence alors à s’exécuter, ce qui renvoie à Java. Le nouveau thread natif n’étant pas attaché à la machine virtuelle Java, il doit être connecté avant d’effectuer le rappel, puis détaché par la suite. La manière habituelle de faire cela semble être de mettre entre crochets le code qui appelle Java avec les appels AttachCurrentThread / DetachCurrentThread. Cela fonctionne bien, mais pour notre application (qui renvoie très fréquemment à Java), le temps nécessaire pour connecter et détacher à chaque fois est important.

Il y a une optimisation décrite à plusieurs endroits (comme ici et ici ) qui recommande d’utiliser des mécanismes basés sur le stockage local des threads pour éliminer ce problème: chaque fois que le rappel natif est déclenché, le thread est testé pour voir s’il est déjà associé au JVM: sinon, il est associé à la machine virtuelle Java et le mécanisme de stockage local du thread est utilisé pour détacher automatiquement le thread lorsqu’il se ferme. Je l’ai implémenté, mais bien que l’attachement et le détachement semblent se produire correctement, cela provoque une fuite des threads du côté Java. Je crois que je fais tout correctement et je me bats pour voir ce qui pourrait être faux. Cela fait un moment que je me casse la tête et je vous serais très reconnaissant de toute idée.

J’ai recréé le problème sous une forme découpée. Vous trouverez ci-dessous le code de la couche native. Ce que nous avons ici est un wrapper qui encapsule le processus de retour d’un pointeur JNIEnv pour le thread en cours, en utilisant le mécanisme de stockage POSIX thread-local pour détacher automatiquement le thread s’il n’était pas déjà connecté. Il existe une classe de rappel qui sert de proxy pour la méthode de rappel Java. (J’ai utilisé le rappel à une méthode Java statique afin d’éliminer la complication supplémentaire liée à la création et à la suppression de références d’objects globales à l’object Java, qui ne sont pas pertinentes pour ce problème). Enfin, il existe une méthode JNI qui, lorsqu’elle est appelée, crée un rappel et crée un nouveau thread natif et attend son achèvement. Ce thread nouvellement créé appelle le rappel une fois puis quitte.

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

Le code Java est très simple: il appelle simplement la méthode native en boucle:

 package com.test.callback; public class CallbackTest { static { System.loadLibrary("Native"); } public void runTest_MultiThreaded(int sortingals) { for (int sortingal = 0; sortingal < 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(); } 

Vous trouverez ci-dessous un exemple de résultat de ce test: vous pouvez voir que, même si la couche native signale que le thread natif est correctement connecté et détaché, un nouveau thread Java est créé chaque fois que le rappel est déclenché:

 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 

Juste pour append: la plate-forme de développement que j’utilise est CentOS 6.3 (64 bits). La version Java est la version de dissortingbution Oracle 1.7.0_45, bien que le problème apparaisse également avec la dissortingbution OpenJDK, versions 1.7 et 1.6.

Oracle a résolu ce problème avec la machine virtuelle Java et celui-ci sera publié sous Java 7, mise à jour 80.

Eh bien, si vous n’acceptez pas votre propre réponse, vous accepterez peut-être celle-ci. Au moins, il n’y aura plus autant de trafic pour une question sans réponses.