2012-05-16 5 views
17

Ho letto su on IBM cheIn JNI, come si memorizzano nella cache la classe, methodID e fieldIDs per le raccomandazioni delle prestazioni di IBM?

Per accedere ai campi oggetti Java e invocare i loro metodi, codice nativo deve effettuare chiamate a FindClass(), GetFieldID(), GetMethodId(), e GetStaticMethodID(). Nel caso di GetFieldID(), GetMethodID() e GetStaticMethodID(), gli ID restituiti per una determinata classe non cambiano per la durata del processo JVM. Ma la chiamata per ottenere il campo o il metodo può richiedere un lavoro significativo nella JVM, perché i campi e i metodi potrebbero essere stati ereditati dalle superclassi, rendendo la JVM risalire la gerarchia delle classi per trovarli. Poiché gli ID sono gli stessi per una determinata classe, dovresti cercarli una volta e poi riutilizzarli. Analogamente, la ricerca di oggetti di classe può essere costosa, pertanto è necessario che siano memorizzati nella cache .

Come si fa a di cache i methodID, fieldID, e class oggetti in JNI? Esistono metodi incorporati o una procedura specifica da seguire?

risposta

32

Non vi sono costruiti -in metodi o procedure specifiche, ma qui è un'implementazione alquanto standard, pulita e ripetibile che mostra come pratico le raccomandazioni di IBM.

Ho intenzione di assumere che tu stia chiamando la tua DLL da Java e la stai usando ciclicamente.

La classe nativa Java è org.stackoverflow.jni.NativeClazz; si useranno anche i metodi integrati 2 JNI JNI_OnLoad e JNI_OnUnload (l'ex esempio ha utilizzato 2 metodi personalizzati: initialize() e destroy()).

vuoto JNI_OnLoad (JavaVM * vm, void * riservato): Questo metodo verrà utilizzato per registrare gli ID di classe come variabili globali e assegnare gli ID di metodo e gli ID campo per variabili statiche. Viene chiamato automaticamente quando il tuo driver viene caricato dalla Java VM e viene richiamato solo una volta durante il ciclo di vita.

vuoto JNI_OnUnload (JavaVM * vm, void * riservato): Questo metodo sarà utilizzato per liberare le variabili globali registrati da JNI_OnLoad. La VM chiama JNI_OnUnload immediatamente prima della chiusura dell'applicazione.

Motivazione: È a mia conoscenza che è necessario registrare gli ID di classe come riferimenti globali per mantenere la validità di qualsiasi ID metodo/ID campo correlato. Se non lo hai fatto e la classe viene scaricata dalla JVM, gli ID metodo/ID campo potrebbero cambiare in ricarica. Gli ID metodo e ID campo non devono essere registrati come riferimenti globali se l'ID classe associato è registrato. La registrazione degli ID di classe come riferimenti globali impedisce la disconnessione della classe Java associata, stabilizzando quindi i valori ID metodo/ID campo. Come riferimenti globali, gli ID di classe devono essere rimossi nel metodo destroy().

ID metodo e ID campo non sono gestiti dal codice nativo; sono gestiti dalla macchina virtuale e sono validi fino a quando la classe associata non viene scaricata. ID campo e ID metodo non possono essere eliminati in modo esplicito prima che la macchina virtuale scarichi la classe di definizione; possono essere lasciati alla VM da gestire dopo lo scaricamento.

codice di esempio

codice segue; i commenti nel file .cpp spiegano la registrazione delle variabili a livello globale.

Ecco la classe Java BeanObject che rappresenta il nostro oggetto di dati:

package org.stackoverflow.data; 

public class BeanObject { 

    String foo = ""; 

    public String getFoo() { 

     return foo; 
    } 
} 

Ecco uno scheletro classe Java NativeClazz:

package org.stackoverflow.jni; 

import org.stackoverflow.data.BeanObject; 

public class NativeClazz { 

    // Static area for forced initialization 
    static { 

     // Load Native Library (C++); calls JNI_OnLoad() 
     System.loadLibrary("Native_Library_File_Name"); 
    }  

    /** 
    * 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 classes. 
    */ 
    public native void instanceNativeMethod(BeanObject bean); 
} 

Qui viene generato il C++ file di intestazione utilizzando javah su NativeClazz:

/* DO NOT EDIT THIS FILE - it is machine generated */ 
#include <jni.h> 
/* Header for class org_stackoverflow_jni_NativeClazz */ 

#ifndef _Included_org_stackoverflow_jni_NativeClazz 
#define _Included_org_stackoverflow_jni_NativeClazz 
#ifdef __cplusplus 
extern "C" { 
#endif 

/* 
* Class:  org_stackoverflow_jni_NativeClazz_staticNativeMethod 
* Method: staticNativeMethod 
* Signature:()V 
*/ 
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_staticNativeMethod 
    (JNIEnv *, jclass, jobject); 

/* 
* Class:  org_stackoverflow_jni_NativeClazz_instanceNativeMethod 
* Method: instanceNativeMethod 
* Signature:()V 
*/ 
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_instanceNativeMethod 
    (JNIEnv *, jobject, jobject); 

#ifdef __cplusplus 
} 
#endif 
#endif 

Ecco il file C++ cpp attuare il file di intestazione:

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

/************************************************************** 
* Declare JNI_VERSION for use in JNI_Onload/JNI_OnUnLoad 
* Change value if a Java upgrade requires it (prior: JNI_VERSION_1_6) 
**************************************************************/ 
static jint JNI_VERSION = JNI_VERSION_1_8; 

/************************************************************** 
* Initialize the static Class and Method Id variables 
**************************************************************/ 
jint JNI_OnLoad(JavaVM* vm, void* reserved) { 

    // Obtain the JNIEnv from the VM and confirm JNI_VERSION 
    JNIEnv* env; 
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK) { 

     return JNI_ERR; 
    } 

    // Temporary local reference holder 
    jclass tempLocalClassRef; 

    // STEP 1/3 : Load the class id 
    tempLocalClassRef = env->FindClass("org/stackoverflow/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. 

    // Return the JNI Version as required by method 
    return JNI_VERSION; 
} 

/************************************************************** 
* Destroy the global static Class Id variables 
**************************************************************/ 
void JNI_OnUnload(JavaVM *vm, void *reserved) { 

    // Obtain the JNIEnv from the VM 
    // NOTE: some re-do the JNI Version check here, but I find that redundant 
    JNIEnv* env; 
    vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION); 

    // Destroy the global references 
    env->DeleteGlobalRef(JC_BeanObject); 

    // ... repeat for any other global references 
} 

/************************************************************** 
* A Static Native Method 
**************************************************************/ 
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_staticNativeMethod 
       (JNIEnv * env, jclass clazz, jobject jBeanObject) { 

    // Retrieve jstring from the Java Object 
    jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo); 

    // Make accessible to C++ 
    const char * cFoo = env->GetStringUTFChars(jFoo, NULL);    

    // Do something with cFoo... 

    // Release Resources 
    env->ReleaseStringUTFChars(jFoo, cFoo); 
    env->DeleteLocalRef(jFoo); 
} 

/************************************************************** 
* Instance/Non-Static Native Method 
**************************************************************/ 
JNIEXPORT void JNICALL Java_org_stackoverflow_jni_NativeClazz_instanceNativeMethod 
       (JNIEnv * env, jobject selfReference, jobject jBeanObject) { 

    // Retrieve jstring from the Java Object 
    jstring jFoo = (jstring)env->CallObjectMethod(jBeanObject, JMID_BeanObject_getFoo);    

    // Make accessible to C++ 
    const char * cFoo = env->GetStringUTFChars(jFoo, NULL);    

    // Do something with cFoo... 

    // Release Resources 
    env->ReleaseStringUTFChars(jFoo, cFoo); 
    env->DeleteLocalRef(jFoo); 
} 
+0

Una delle migliori risposte su caching jclass/jmethodId/jfieldId! Ancora una domanda mi infastidisce. Che dire di jmethodID/jfieldID? Sono strutture opache, come possiamo liberarle/cancellarle o dovremmo? O semplicemente puntare tutto (tutti i ref rifuiti globali, jmethodIDs, jfieIDs) a NULL/nullptr è sufficiente? –

+1

@KonstantinBerkow - Gli ID metodo e ID campo non sono gestiti dal codice nativo; sono gestiti dalla macchina virtuale e sono validi fino a quando la classe associata non viene scaricata. ID campo e ID metodo non possono essere eliminati in modo esplicito prima che la macchina virtuale scarichi la classe di definizione. Lascia che sia la VM a gestirli. Se ti fa sentire meglio impostarli su NULL, fallo dopo aver rilasciato la classe associata, ma non è affatto necessario. – JoshDM

3

Si può avere alcune strutture di utilità come questo:

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; 
} 

questo è preso dalla mia domanda: https://stackoverflow.com/questions/10617714/how-to-extend-swt-com-support

Per alcuni buoni esempi guardare il os_structs.c è in bundle con l'implementazione di Eclipse SWT.

Nota: Il codice sopra riportato è solo un esempio e potrebbe essere adattato per diversi sistemi operativi. Inoltre mostra solo "come accedere ai campi java"; per i metodi potresti seguire lo stesso approccio.

+4

Questo codice non può assolutamente funzionare. Non è possibile memorizzare in cache jclasses o jobject tra diverse chiamate JNI senza utilizzare GlobalReference. È possibile memorizzare ID metodoID e ID campo. – EJP

+0

@EJP: Potresti spiegare perché 'non può memorizzare in cache jclasses o jobject attraverso JNI diversi'? Lo snippet di codice qui fornito viene preso e modificato dall'implementazione SWT di eclissi per win32. – Favonius

+3

@Favonious Per [lo stesso motivo per cui esistono GlobalReferences] (http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp1242). (1) "I riferimenti locali sono validi per la durata di una chiamata al metodo nativo", (2) "Tutti gli oggetti Java restituiti dalle funzioni JNI sono riferimenti locali" e (3) "I riferimenti locali sono validi solo nel thread in cui sono sono creati". – EJP

0

Per il caso semplice, io uso campi statici nel codice C, inizializzati con un metodo initid chiamato dalle mie classi Java:

package demo; 
public class WithNatives { 

static { 
    initIDs(); 
} 

private static native void initIDs(); 

} 

E in C:

static jmethodID methodId; 

void JNICALL Java_demo_WithNatives_initIDs(JNIEnv *env, jclass clazz) 
{ 
    // initialize methodId 
} 
+0

Ma non si mostra come eseguire la memorizzazione nella cache. – mlepage

+0

Quello che non ho mostrato è come inizializzare il campo 'methodId', ma è JNI di base. La memorizzazione nella cache viene eseguita automaticamente durante il caricamento della classe: viene richiamato initIDs e il campo 'methodID' è inizializzato – Julien

7

Ecco come ho pratica la raccomandazione di IBM. Considerando demo classe java così:

public class SimpleClazz { 

    public int value = 10; 

    public native int getValue(); 

    static { 
     // Load Native Library 
     System.loadLibrary("The native library name"); 
    } 
} 

Il corrispondente file di intestazione jni così:

/* 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 

Secondo la raccomandazione di IBM, abbiamo bisogno di memorizzare nella cache il classe utilizzata SimpleClazz e il campo ID dell'oggetto membro value.

Dopo aver appreso questo bene article, memorizzo nella cache lo SimpleClazz nella funzione JNI_OnLoad, che viene richiamata quando viene caricata la libreria nativa (ad esempio, tramite System.loadLibrary). Nel numero JNI_Onload, troviamo la classe e archiviamo questa jclass come un campo globale.

Inoltre, nell'implementazione nativa di getValue, viene utilizzata la variabile locale statica per memorizzare l'ID campo di value. Questo design è per assicurarsi che questo ID archiviato possa essere nell'ambito migliore, piuttosto che nell'ambito globale. Lo svantaggio di questo design è che dobbiamo confrontarci con NULL ogni volta che chiamiamo questa funzione. Ho imparato questo disegno dalla sezione 4.4.1 del libro The Java Native Interface: Programmer's Guide and Specification.

Infine, abbiamo anche bisogno di scrivere la funzione JNI_OnUnload, che viene chiamata quando il caricatore di classi contenente la libreria nativa è garbage collection. In questa funzione, rilasciamo il riferimento globale di jclass.

mia implementazione cpp è mostrato come di seguito:

#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) { 
    JNIEnv* env; 
    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); 
     } 
    } 
} 
+0

È consigliabile eseguire questa memorizzazione nella cache nel metodo JNI_OnLoad, in particolare in Android, perché ClassLoader è disponibile solo (per impostazione predefinita) sul thread che sta chiamando JNI_OnLoad, ecco perché ho svottolato questa risposta! – yano

Problemi correlati