java performance caching jni

java - En JNI, ¿cómo puedo almacenar en caché la clase, ID de método e ID de campo según las recomendaciones de rendimiento de IBM?



performance caching (4)

Leí en IBM que

Para acceder a los campos de objetos Java e invocar sus métodos, el código nativo debe realizar llamadas a FindClass (), GetFieldID (), GetMethodId () y GetStaticMethodID (). En el caso de GetFieldID (), GetMethodID () y GetStaticMethodID (), los ID devueltos para una clase determinada no cambian durante el tiempo de vida del proceso de JVM. Pero el llamado para obtener el campo o el método puede requerir un trabajo significativo en la JVM, ya que los campos y los métodos podrían haberse heredado de las superclases, lo que hace que la JVM suba la jerarquía de clases para encontrarlos. Debido a que los ID son los mismos para una clase determinada, debe buscarlos una vez y luego volver a usarlos. Del mismo modo, buscar objetos de clase puede ser costoso, por lo que también deben almacenarse en caché.

¿Cómo se almacenan en caché los methodID , fieldID y objetos de class en JNI? ¿Hay métodos integrados o un procedimiento específico que se debe seguir?


Aquí es cómo practico la recomendación de IBM. Teniendo en cuenta la clase demo java así:

public class SimpleClazz { public int value = 10; public native int getValue(); static { // Load Native Library System.loadLibrary("The native library name"); } }

El archivo de encabezado jni correspondiente así:

/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class SimpleClazz */ #ifndef _Included_SimpleClazz #define _Included_SimpleClazz #ifdef __cplusplus extern "C" { #endif /* * Class: SimpleClazz * Method: getValue * Signature: ()I */ JNIEXPORT jint JNICALL Java_SimpleClazz_getValue (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif

De acuerdo con la recomendación de IBM, debemos almacenar en caché la clase utilizada SimpleClazz y el identificador de campo del value miembro del objeto.

Después de aprender este buen artículo , SimpleClazz en memoria caché el SimpleClazz en la función JNI_OnLoad , que se JNI_OnLoad cuando se carga la biblioteca nativa (por ejemplo, a través de System.loadLibrary). En JNI_Onload , encontramos clases y almacenamos este jclass como un campo global.

Además, en la implementación nativa de getValue , utilizamos una variable local estática para almacenar en caché el ID de campo del value . Este diseño es para asegurarse de que esta identificación archivada pueda estar en el mejor alcance, en lugar de en el alcance global. El inconveniente de este diseño es que tenemos que comparar con NULL cada vez que llamamos a esta función. Aprendí este diseño de la sección 4.4.1 del libro The Java Native Interface: Programmer''s Guide and Specification .

Finalmente, también necesitamos escribir la función JNI_OnUnload , que se JNI_OnUnload cuando el cargador de clases que contiene la biblioteca nativa es basura. En esta función, lanzamos la referencia global de jclass.

Mi implementación de cpp se muestra a continuación:

#include <jni.h> #include <SimpleClazz.h> static jclass simpleCls; // According to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#JNI_OnLoad // The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). jint JNI_OnLoad(JavaVM* vm, void* reserved) { if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } else { jclass localSimpleCls = (*env)->FindClass("SimpleClazz"); if (localSimpleCls == NULL) { return JNI_ERR; } simpleCls = (jclass) (*env)->NewGlobalRef(env, localSimpleCls); } return JNI_VERSION_1_6; } JNIEXPORT jint JNICALL Java_SimpleClazz_getValue(JNIEnv * env, jobject thiz){ static jfieldID valueID = NULL; if (valueID == NULL) { valueID = (*env)->GetFieldID(env, simpleCls, "value", "I"); if (valueID == NULL){ return JNI_ERR; // Exception thrown } } jint value = (*env)->GetIntField(env, thiz, valueID); return value; } // According to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html#JNI_OnUnload // The VM calls JNI_OnUnload when the class loader containing the native library is garbage collected. void JNI_OnUnload(JavaVM *vm, void *reserved) { JNIEnv* env; if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) { // Something is wrong but nothing we can do about this :( return; } else { if (0 != NULL){ (*env)->DeleteGlobalRef(env, simpleCls); } } }


No hay métodos incorporados ni procedimientos específicos, pero aquí hay una implementación algo estándar, limpia y repetible que muestra cómo practico la recomendación de IBM.

Voy a suponer que está llamando a su archivo DLL desde Java y lo está usando cíclicamente.

Su Native Java Class org..jni.NativeClazz debe tener 2 métodos nativos declarados: initialize() y destroy() .

initialize (): la intención del método initialize() es registrar sus ID de clase como variables globales y asignar sus ID de método e ID de campo a las variables estáticas. Se debe NativeClazz en o inmediatamente después de la instanciación de su objeto NativeClazz ; conceptualmente debería ser lo primero que se llamó, y solo se llamó una vez durante el ciclo de vida del Objeto. Como alternativa a initialize() , en su lugar puede optar por anular JNI_OnLoad() .

destroy (): la intención del método destroy() es liberar las variables globales registradas por initialize() . destroy() debe llamarse inmediatamente antes del cierre de la aplicación, posiblemente registrándolo en un oyente; formas simples también incluyen llamarlo desde un método de servlet destroy() , o si usa Spring, registrar NativeClazz en el contexto de la aplicación e implementar DisposableBean . Como alternativa a destroy() , en su lugar puede optar por anular JNI_OnUnLoad() .

Justificación: Según tengo entendido, debe registrar los ID de clase como referencias globales para mantener la viabilidad de cualquier Id. De método / ID de campo relacionados. Si no hizo esto y la clase se descarga de la JVM, los ID de método / ID de campo pueden cambiar al volver a cargar. Los ID de método y los ID de campo no necesitan registrarse como referencias globales si se registra la ID de clase asociada. El registro de identificadores de clase como referencias globales impide que la clase Java asociada se descargue, estabilizando por lo tanto los valores ID de método / ID de campo. Como referencias globales, las identificaciones de clase deben eliminarse en el método destroy() .

El código sigue; los comentarios en el archivo .cpp explican el registro global de sus variables.

Aquí está la clase de Java BeanObject representa nuestro objeto de datos:

package org..data; public class BeanObject { String foo = ""; public String getFoo() { return foo; } }

Aquí hay un esqueleto clase Java NativeClazz :

package org..jni; import org..data.BeanObject; public class NativeClazz { // Static area for forced initialization static { // Load Native Library (C++) System.loadLibrary("Native_Library_File_Name"); // Load Class and Method Ids initialize(); } /** * Native Initialization loads ClassIDs and MethodIDs to static variables. */ public static native void initialize(); /** * Native Destructor unloads ClassIDs from global memory. */ public static native void destroy(); /** * A static native method you plan to call. */ public static native void staticNativeMethod(BeanObject bean); /** * A non-static native method you plan to call, to show this also works with * instantiated Java objects. */ public native void instanceNativeMethod(BeanObject bean); }

Aquí está el archivo de encabezado que javah crea desde NativeClazz :

/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class org__jni_NativeClazz */ #ifndef _Included_org__jni_NativeClazz #define _Included_org__jni_NativeClazz #ifdef __cplusplus extern "C" { #endif /* * Class: org__jni_NativeClazz * Method: initialize * Signature: ()V */ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_initialize (JNIEnv *, jclass); /* * Class: org__jni_NativeClazz * Method: destroy * Signature: ()V */ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_destroy (JNIEnv *, jclass); /* * Class: org__jni_NativeClazz_staticNativeMethod * Method: staticNativeMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_staticNativeMethod (JNIEnv *, jclass, jobject); /* * Class: org__jni_NativeClazz_instanceNativeMethod * Method: instanceNativeMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_instanceNativeMethod (JNIEnv *, jobject, jobject); #ifdef __cplusplus } #endif #endif

Aquí está el archivo CPP creado usando la clase de encabezado:

#include "org__jni_NativeClazz.h" using namespace std; /************************************************************** * Static Global Variables to cache Java Class and Method IDs **************************************************************/ static jclass JC_BeanObject; static jmethodID JMID_BeanObject_getFoo; /************************************************************** * Initialize the static Class and Method Id variables **************************************************************/ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_initialize (JNIEnv * env, jclass clazz) { // Temporary local reference holder jclass tempLocalClassRef; // STEP 1/3 : Load the class id tempLocalClassRef = env->FindClass("org//data/BeanObject"); // STEP 2/3 : Assign the ClassId as a Global Reference JC_BeanObject = (jclass) env->NewGlobalRef(tempLocalClassRef); // STEP 3/3 : Delete the no longer needed local reference env->DeleteLocalRef(tempLocalClassRef); // Load the method id JMID_BeanObject_getFoo = env->GetMethodID(JC_BeanObject, "getFoo", "(Ljava/lang/String;)V"); // ... repeat prior line for any other methods of BeanObject // ... repeat STEPS 1-3 for any other classes; re-use tempLocalClassRef. } /************************************************************** * Destroy the global static Class Id variables **************************************************************/ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_destroy (JNIEnv * env, jclass clazz) { // Destroy the global references env->DeleteGlobalRef(JC_BeanObject); // ... repeat for any other global references } /************************************************************** * A Static Native Method **************************************************************/ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_staticNativeMethod (JNIEnv * env, jclass clazz, jobject jBeanObject) { jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo); } /************************************************************** * Instance / Non-Static Native Method **************************************************************/ JNIEXPORT void JNICALL Java_org__jni_NativeClazz_instanceNativeMethod (JNIEnv * env, jobject selfReference, jobject jBeanObject) { jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo); }


Para el caso simple, uso campos estáticos en el código C, inicializado con un método initId llamado desde mis clases Java:

package demo; public class WithNatives { static { initIDs(); } private static native void initIDs(); }

Y en C:

static jmethodID methodId; void JNICALL Java_demo_WithNatives_initIDs(JNIEnv *env, jclass clazz) { // initialize methodId }


Puede tener algunas estructuras de utilidad como esta:

typedef struct MYVARIANT_FID_CACHE { int cached; jclass clazz; jfieldID pAddress; } MYVARIANT_FID_CACHE; MYVARIANT_FID_CACHE VARIANTFc; void cacheMYVARIANTFields(JNIEnv *env, jobject lpObject) { if (VARIANTFc.cached) return; VARIANTFc.clazz = env->GetObjectClass(lpObject); VARIANTFc.pAddress = env->GetFieldID(VARIANTFc.clazz, "pAddress", "I"); VARIANTFc.cached = 1; } VARIANT *getMYVARIANTFields(JNIEnv *env, jobject lpObject, VARIANT *lpStruct) { if (!VARIANTFc.cached) cacheVARIANT2Fields(env, lpObject); lpStruct = (VARIANT*)(env->GetIntField(lpObject, VARIANTFc.pAddress)); return lpStruct; }

Esto está tomado de mi pregunta: https://.com/questions/10617714/how-to-extend-swt-com-support

Para algunos buenos ejemplos, consulte os_structs.c que se incluye con la implementación de eclipse SWT.

Nota: El código anterior es solo un ejemplo y podría adaptarse para diferentes sistemas operativos. También muestra "cómo acceder a los campos de Java"; para los métodos, podrías seguir el mismo enfoque.