Domanda

Quando sto costruendo un oggetto Java usando i metodi JNI, per passarlo come parametro a un metodo Java sto invocando usando l'API di invocazione JNI, come gestisco la sua memoria?

Ecco con cosa sto lavorando:

Ho un oggetto C che ha un metodo distruttore più complesso di free () . Questo oggetto C deve essere associato a un oggetto Java e, una volta terminata l'applicazione con l'oggetto Java, non ho più bisogno dell'oggetto C.

Sto creando l'oggetto Java in questo modo (controllo degli errori eluito per chiarezza):

c_object = c_object_create ();
class = (*env)->FindClass (env, "my.class.name");
constructor = (*env)->GetMethodID (env, class, "<init>", "(J)V");
instance = (*env)->NewObject (env, class, constructor, (jlong) c_object);

method = (*env)->GetMethodID (env, other_class, "doSomeWork", "(Lmy.class.name)V");
(*env)->CallVoidMethod (env, other_class, method, instance);

Quindi, ora che ho finito con istanza , cosa ci faccio? Idealmente, vorrei lasciare la garbage collection nella VM; una volta terminato con istanza sarebbe fantastico se si chiamasse anche c_object_destroy () sul puntatore che gli ho fornito. È possibile?

Una domanda separata, ma correlata ha a che fare con l'ambito delle entità Java che creo in un metodo come questo; devo rilasciare manualmente, diciamo, class , costruttore o metodo sopra? Il documento JNI è frustrantemente vago (a mio giudizio) in materia di corretta gestione della memoria.

È stato utile?

Soluzione

Esistono un paio di strategie per recuperare risorse native (oggetti, descrittori di file, ecc.)

  1. Richiama un metodo JNI durante finalize () che libera la risorsa. Alcune persone sconsigliano di implementare la finalizzazione , e in pratica non puoi davvero essere sicuro che la tua risorsa nativa sia mai liberata. Per risorse come la memoria questo non è probabilmente un problema, ma se ad esempio hai un file che deve essere scaricato in un momento prevedibile, finalizzare () probabilmente non è una buona idea.

  2. Richiama manualmente un metodo di pulizia. Questo è utile se hai un momento nel quale sai che la risorsa deve essere ripulita. Ho usato questo metodo quando avevo una risorsa che doveva essere deallocata prima di scaricare una DLL nel codice JNI. Per consentire la successiva ricarica della DLL, dovevo essere sicuro che l'oggetto fosse realmente deallocato prima di tentare di scaricare la DLL. Usando solo finalize (), non avrei ottenuto questo garantito. Questo può essere combinato con (1) per consentire l'allocazione della risorsa durante finalize () o nel metodo di pulizia chiamato manualmente. (Probabilmente hai bisogno di una mappa canonica di WeakReferences per tenere traccia di quali oggetti devono essere invocati per il loro metodo di pulizia.)

  3. Presumibilmente il PhantomReference può essere usato anche per risolvere questo problema, ma non sono sicuro di come funzionerebbe tale soluzione.

In realtà, non sono d'accordo con te sulla documentazione di JNI. Trovo specifica JNI eccezionalmente chiarire la maggior parte delle questioni importanti, anche se le sezioni sulla gestione dei riferimenti locali e globali avrebbero potuto essere più elaborate.

Altri suggerimenti

Le specifiche JNI trattano il problema di chi "possiede" Oggetti Java creati con metodi JNI qui . Devi distinguere tra riferimenti locali e globali .

Quando JVM effettua una chiamata JNI al codice nativo, imposta un registro per tenere traccia di tutti gli oggetti creati durante la chiamata. Qualsiasi oggetto creato durante la chiamata nativa (ovvero restituito da una funzione di interfaccia JNI) viene aggiunto a questo registro. I riferimenti a tali oggetti sono noti come riferimenti locali . Quando il metodo nativo ritorna alla JVM, tutti i riferimenti locali creati durante la chiamata del metodo nativo vengono distrutti. Se si effettuano chiamate indietro nella JVM durante una chiamata del metodo nativo, il riferimento locale sarà ancora attivo quando il controllo torna al metodo nativo. Se la JVM invocata dal codice nativo effettua un'altra chiamata nel codice nativo, viene creato un nuovo registro di riferimenti locali e si applicano le stesse regole.

(In effetti, puoi implementare il tuo eseguibile JVM (ad esempio java.exe) usando l'interfaccia JNI, creando una JVM (ricevendo quindi un puntatore JNIEnv *), cercando la classe indicata nella riga di comando, e invocando il metodo main () su di esso.)

Tutti i riferimenti restituiti dai metodi di interfaccia JNI sono locali . Ciò significa che in circostanze normali non è necessario deallocare manualmente i riferimenti restituiti dai metodi JNI, poiché vengono distrutti quando si ritorna alla JVM. A volte vuoi comunque distruggerli "prematuramente", ad esempio quando hai molti riferimenti locali che vuoi eliminare prima di tornare alla JVM.

I riferimenti globali vengono creati (dai riferimenti locali) utilizzando NewGlobalRef (). Sono aggiunti a un registro speciale e devono essere deallocati manualmente. I riferimenti globali vengono utilizzati solo per l'oggetto Java a cui il codice nativo deve contenere un riferimento a più chiamate JNI, ad esempio se si dispone di eventi di attivazione del codice nativo che devono essere propagati a Java. In tal caso, il codice JNI deve memorizzare un riferimento a un oggetto Java che deve ricevere l'evento.

Spero che questo chiarisca un po 'il problema della gestione della memoria.

Ri: "Una domanda separata ma correlata" ... non è necessario rilasciare manualmente jclass, jfieldID e jmethodID quando li si utilizza in un "locale" contesto. Eventuali riferimenti a oggetti reali ottenuti (non jclass, jfieldID, jmethodID) devono essere rilasciati con DeleteLocalRef.

Il GC raccoglierà la tua istanza, ma non rilascerà automaticamente la memoria heap non java allocata nel codice nativo. Dovresti avere un metodo esplicito nella tua classe per rilasciare l'istanza di c_object.

Questo è uno dei casi in cui consiglierei di usare un finalizzatore per verificare se c_object è stato rilasciato e rilasciarlo, registrando un messaggio se non lo è.

Una tecnica utile è quella di creare un'istanza Throwable nel costruttore della classe Java e archiviarla in un campo (o semplicemente inizializzare il campo in linea). Se il finalizzatore rileva che la classe non è stata disposta correttamente, stampa lo stacktrace, individuando lo stack di allocazione.

Un suggerimento è di evitare di fare JNI direttamente e andare con gluegen o Swig (entrambi generano codice e possono essere collegati staticamente).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top