java - reservada - jni types
¿Por qué no debería volver a utilizar un jclass y/o jmethodID en JNI? (5)
Como otros ya escribieron
- Puede almacenar
jmethodID
en una variable estática de C ++ sin problemas - Puede almacenar local
jobject
ojclass
en una variable estática de C ++ después de convertirlos en objetos globales llamando aenv->NewGloablRef()
Solo quiero agregar aquí una información adicional: la razón principal para almacenar jclass en una variable estática de C ++ será que usted cree que es un problema de rendimiento llamar a env->FindClass()
cada vez.
Pero FindClass()
la velocidad de FindClass()
con un contador de rendimiento con la API QueryPerformanceCounter()
en Windows. Y el resultado fue asombroso:
En mi computadora con una CPU de 3,6 GHz, la ejecución de
jcass p_Container = env->FindClass("java/awt/Container");
toma entre 0,01 ms y 0,02 ms. Eso es increíblemente rápido. Miré el código fuente de Java y usan un diccionario donde están almacenadas las clases. Esto parece implementarse de manera muy eficiente.
Probé algunas clases más y aquí está el resultado:
Elapsed 0.002061 ms for java/net/URL
Elapsed 0.044390 ms for java/lang/Boolean
Elapsed 0.019235 ms for java/lang/Character
Elapsed 0.018372 ms for java/lang/Number
Elapsed 0.017931 ms for java/lang/Byte
Elapsed 0.017589 ms for java/lang/Short
Elapsed 0.017371 ms for java/lang/Integer
Elapsed 0.015637 ms for java/lang/Double
Elapsed 0.018173 ms for java/lang/String
Elapsed 0.015895 ms for java/math/BigDecimal
Elapsed 0.016204 ms for java/awt/Rectangle
Elapsed 0.016272 ms for java/awt/Point
Elapsed 0.001817 ms for java/lang/Object
Elapsed 0.016057 ms for java/lang/Class
Elapsed 0.016829 ms for java/net/URLClassLoader
Elapsed 0.017807 ms for java/lang/reflect/Field
Elapsed 0.016658 ms for java/util/Locale
Elapsed 0.015720 ms for java/lang/System
Elapsed 0.014669 ms for javax/swing/JTable
Elapsed 0.017276 ms for javax/swing/JComboBox
Elapsed 0.014777 ms for javax/swing/JList
Elapsed 0.015597 ms for java/awt/Component
Elapsed 0.015223 ms for javax/swing/JComponent
Elapsed 0.017385 ms for java/lang/Throwable
Elapsed 0.015089 ms for java/lang/StackTraceElement
Los valores anteriores provienen de la cadena de distribución de eventos de Java. Si ejecuto el mismo código en un hilo de Windows nativo que ha sido creado por CreateThread()
por mí, se ejecuta incluso 10 veces más rápido . ¿Por qué?
Por lo tanto, si no llama a FindClass()
mucha frecuencia, no hay ningún problema para FindClass()
cuando se llame a su función JNI en lugar de crear una referencia global y almacenarla en una variable estática.
Otro tema importante es la seguridad de hilos . En Java cada hilo tiene su propia estructura independiente JNIEnv
.
- Global
jobject
ojclass
son válidos en cualquier hilo de Java. - Los objetos locales solo son válidos en una invocación de función en el
JNIEnv
del subproceso que realiza la llamada y son basura recopilada cuando el código JNI vuelve a Java.
Ahora depende de los hilos que está utilizando: si registra su función C ++ con env->RegisterNatives()
y el código Java llama a sus funciones JNI, entonces debe almacenar todos los objetos, que quiera usar más adelante, como objetos globales de lo contrario, obtendrán basura recolectada.
Pero si crea su propio hilo con la API CraeteThread()
(en Windows) y obtiene la estructura JNIEnv
llamando a AttachCurrentThreadAsDaemon()
entonces se aplicarán completamente otras reglas: como es su propio hilo, nunca devuelve el control a Java, la basura collector nunca limpiará los objetos que haya creado en su hilo, ¡e incluso podrá almacenar objetos locales en variables estáticas de C ++ sin problemas! (Pero no se puede acceder a estos desde otros subprocesos) En este caso, es extremadamente importante que limpie TODAS sus instancias locales manualmente con env->DeleteLocalRef()
contrario, tendrá una pérdida de memoria.
Recomiendo encarecidamente que cargue TODOS los objetos locales en una clase contenedora que llame a DeleteLocalRef()
en su destructor. Esta es una forma a prueba de balas para evitar fugas de memoria.
Desarrollar código JNI puede ser muy engorroso y puede obtener bloqueos que no comprende. Para encontrar la causa de un bloqueo, abra una ventana de DOS e inicie su aplicación Java con el siguiente comando:
java -Xcheck:jni -jar MyApplication.jar
entonces verás qué problemas han sucedido en el código JNI. Por ejemplo:
FATAL ERROR in native method: Bad global or local ref passed to JNI
y encontrará el stacktrace en el archivo de registro que Java crea en la misma carpeta donde tiene el archivo JAR:
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x6e8655d5, pid=4692, tid=4428
#
etc...
Esta es una pregunta relacionada con una publicación anterior , pero esta publicación se resolvió y ahora quería cambiar la dirección de la pregunta.
Cuando se trabaja con JNI, es necesario preguntar al objeto JNIEnv
por jclass
y jmethodID
para cada clase y método que se usará en el código C / C ++. Para que quede claro, quiero llamar a los contructors de Java o a los métodos de C / C ++.
Como la comunicación de Java a C / C ++ (y viceversa) es costosa, inicialmente pensé que una forma de minimizar esto era reutilizar jclass
y jmethodID
. Por lo tanto, guardé estas instancias en variables globales de la siguiente manera:
jclass someClass = NULL;
jmethodID someMethod = NULL;
JNIEXPORT jobject JNICALL Java_example_method1(JNIEnv *env, jobject jobj) {
// initialize someClass and someMethod if they are NULL
// use someClass and someMethod to call java (for example, thru NewObject)
}
JNIEXPORT jobject JNICALL Java_example_method2(JNIEnv *env, jobject jobj) {
// initialize someClass and someMethod if they are NULL
// use someClass and someMethod to call java again
}
Un ejemplo más específico (y útil), que utilizo para lanzar excepciones desde cualquier lugar en mis funciones JNI:
jclass jniExceptionClass = NULL;
void throwJavaException(JNIEnv *env, const char* msg) {
if (!jniExceptionClass) {
jniExceptionClass = env->FindClass("example/JNIRuntimeException");
}
if (jniExceptionClass)
env->ThrowNew(jniExceptionClass, msg);
}
}
El problema es que continué usando este patrón y obtuve una falla de segmentación que solo fue resuelta al no reutilizar estas variables (esta fue la solución a la publicación anterior).
Las preguntas son:
- ¿Por qué es ilegal reutilizar
jclass
yjmethodID
través de diferentes funciones de JNI? Pensé que estos valores siempre eran los mismos. - Solo por curiosidad: ¿cuál es el impacto / sobrecarga de inicializar todos los
jclass
yjmethodID
necesarios para cada función JNI?
Como recuerdo, jclass será local para el método de llamada, por lo que no se puede almacenar en caché, sin embargo, el ID del método puede ser. Consulte http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/design.html para obtener más información detallada.
Lo siento, no sé sobre el aspecto del rendimiento, cada vez que he usado JNI ha sido insignificante en comparación con la tarea en cuestión.
Dentro de JNI_OnLoad
, debe usar NewGlobalRef
en los valores de jclass
devueltos por FindClass
antes de almacenarlos en caché.
Luego, dentro de JNI_OnUnload
usted llama DeleteGlobalRef
en ellos.
Las reglas aquí son claras. Los ID de método y los valores de ID de campo son para siempre. Puedes aferrarte a ellos. Las búsquedas toman algo de tiempo.
jclass
, por otro lado, es generalmente una referencia local . Una referencia local sobrevive, como máximo, a la duración de una sola llamada a una función JNI.
Si necesita optimizar, debe pedirle a la JVM que haga una referencia global por usted. No es raro adquirir y mantener referencias a clases comunes como java.lang.String
.
La celebración de una referencia de este tipo a una clase evitará que (la clase) se recolecte basura, por supuesto.
jclass local = env->FindClass(CLS_JAVA_LANG_STRING);
_CHECK_JAVA_EXCEPTION(env);
java_lang_string_class = (jclass)env->NewGlobalRef(local);
_CHECK_JAVA_EXCEPTION(env);
env->DeleteLocalRef(local);
_CHECK_JAVA_EXCEPTION(env);
La macro de verificación llama:
static inline void
check_java_exception(JNIEnv *env, int line)
{
UNUSED(line);
if(env->ExceptionOccurred()) {
#ifdef DEBUG
fprintf(stderr, "Java exception at rlpjni.cpp line %d/n", line);
env->ExceptionDescribe();
abort();
#endif
throw bt_rlpjni_java_is_upset();
}
}
Puede almacenar en caché y utilizar sus ID de método / función / clase y usarlos de forma segura si lo codifica de forma adecuada. He respondido una pregunta similar aquí en SO y he publicado un código explícito sobre cómo seguir las recomendaciones de rendimiento de caché de IBM.