java - hilos - jtapi ejemplo
Se produce un error en el hilo de Java al devolver la llamada desde un hilo nativo a través de JNI (1)
Oracle ha solucionado este problema con la JVM, y se lanzará en la actualización 80 de Java 7.
Bueno, si no vas a aceptar tu propia respuesta, tal vez aceptes esta. Por lo menos ya no generará tanto tráfico para una pregunta de respuestas cero.
Resumen: veo pérdidas de subprocesos de Java al volver a llamar a Java desde un código nativo en un subproceso creado originalmente.
(Actualización del 11 de febrero de 2014: planteamos esto como una solicitud de soporte con Oracle. Ahora Oracle ha confirmed en Java 7 actualización 45. Solo afecta a las plataformas Linux de 64 bits (y posiblemente a Mac): Linux de 32 bits no se ve afectado) .
(Actualización 29 de abril de 2014: Oracle tiene una solución para este problema, y se lanzará en Java 7 actualización 80).
Tengo una aplicación que consiste en una capa de Java y una biblioteca nativa. La capa Java llama a la biblioteca nativa a través de JNI: esto hace que un nuevo hilo nativo comience a ejecutarse, lo que hace que vuelva a llamar a Java. Debido a que el nuevo subproceso nativo no está conectado a la JVM, debe conectarse antes de realizar la devolución de llamada, y luego desconectarse. La forma habitual de hacer esto parece ser poner entre corchetes el código que devuelve la llamada a Java con AttachCurrentThread / DetachCurrentThread llamadas. Esto funciona bien, pero para nuestra aplicación (que vuelve a llamar a Java con mucha frecuencia) la sobrecarga de adjuntar y separar cada vez es significativa.
Hay una optimización descrita en varios lugares (como here y here ) que recomienda el uso de mecanismos basados en el almacenamiento local de subprocesos para eliminar este problema: básicamente, cada vez que se activa la devolución de llamada nativa, el subproceso se prueba para ver si ya está conectado al JVM: si no, está conectado a la JVM y el mecanismo de almacenamiento local de subprocesos se utiliza para separar automáticamente el subproceso cuando se cierra. Lo he implementado, pero a pesar de que el adjuntar y separar parece estar ocurriendo correctamente, esto provoca una fuga de subprocesos en el lado de Java. Creo que estoy haciendo todo correctamente y estoy luchando por ver qué podría estar mal. He estado criticando esto por un tiempo y estaría muy agradecido por cualquier idea.
He recreado el problema en forma reducida. A continuación está el código para la capa nativa. Lo que tenemos aquí es un contenedor que encapsula el proceso de devolver un puntero JNIEnv para el hilo actual, utilizando el mecanismo de almacenamiento local de hilos POSIX para separar automáticamente el hilo si no estaba ya conectado. Hay una clase de devolución de llamada que actúa como un proxy para el método de devolución de llamada de Java. (He utilizado la devolución de llamada a un método Java estático para eliminar la complicación adicional de crear y eliminar referencias de objetos globales al objeto Java, que son irrelevantes para este problema). Finalmente hay un método JNI que cuando se llama, construye una devolución de llamada y crea un nuevo hilo nativo y espera a que se complete. Este hilo recién creado llama a la devolución de llamada una vez que luego sale.
#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;
}
}
}
El código de Java es muy simple: simplemente llama repetidamente al método nativo en un bucle:
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();
}
A continuación se muestra un ejemplo de resultado de esta prueba: puede ver que aunque la capa nativa informa que el hilo nativo se está conectando y desconectando con éxito, cada vez que se activa la devolución de llamada se crea un nuevo hilo 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
Solo para agregar: la plataforma de desarrollo que estoy usando es CentOS 6.3 (64 bits). La versión de Java es la versión de distribución de Oracle 1.7.0_45, aunque el problema también se muestra con la distribución de OpenJDK, las versiones 1.7 y 1.6.