Pregunta

Cuando estoy creando un objeto java usando métodos JNI, para pasarlo como un parámetro a un método java, invoco usando la API de invocación JNI, ¿cómo administro su memoria?

Esto es con lo que estoy trabajando:

Tengo un objeto C que tiene un método destructor que es más complejo que free () . Este objeto C debe asociarse con un objeto Java, y una vez que la aplicación termina con el objeto Java, ya no necesito más el objeto C.

Estoy creando el objeto Java como tal (la comprobación de errores no es clara):

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

Entonces, ahora que he terminado con instance , ¿qué hago con eso? Idealmente, me gustaría dejar la recolección de basura a la máquina virtual; cuando termine con instancia sería fantástico si también se llamara c_object_destroy () en el puntero que le proporcioné. ¿Es esto posible?

Una pregunta separada, pero relacionada, tiene que ver con el alcance de las entidades Java que creo en un método como este; ¿Tengo que liberar manualmente, digamos, class , constructor o method arriba? El documento JNI es frustrantemente vago (en mi opinión) sobre el tema de la gestión de memoria adecuada.

¿Fue útil?

Solución

Hay un par de estrategias para reclamar recursos nativos (objetos, descriptores de archivos, etc.)

  1. Invoque un método JNI durante finalize () que libera el recurso. Algunas personas recomiendan no implementar Finalize , y básicamente no puede estar seguro que su recurso nativo se libera alguna vez. Para recursos como la memoria, es probable que esto no sea un problema, pero si tiene un archivo, por ejemplo, que se debe vaciar en un momento predecible, finalize () probablemente no sea una buena idea.

  2. Invoque manualmente un método de limpieza. Esto es útil si tiene un punto en el tiempo en el que sabe que el recurso se debe limpiar. Utilicé este método cuando tenía un recurso que tenía que ser desasignado antes de descargar una DLL en el código JNI. Para permitir que la DLL se vuelva a cargar más tarde, tenía que estar seguro de que el objeto realmente se había desasignado antes de intentar descargar la DLL. Usando solo finalize (), no hubiera conseguido esto garantizado. Esto se puede combinar con (1) para permitir que el recurso se asigne durante la finalización () o en el método de limpieza llamado manualmente. (Es probable que necesite un mapa canónico de WeakReferences para rastrear qué objetos necesitan que se invoque su método de limpieza).

  3. Supuestamente, la PhantomReference también se puede utilizar para resolver este problema, pero no estoy seguro de cómo funcionaría tal solución.

En realidad, tengo que estar en desacuerdo con usted en la documentación de JNI. Encuentro la especificación JNI excepcionalmente aclare la mayoría de los temas importantes, incluso si las secciones sobre la gestión de referencias locales y globales podrían haber sido más detalladas.

Otros consejos

La especificación JNI cubre el problema de quién " posee " Los objetos Java creados en los métodos JNI aquí . Debe distinguir entre las referencias locales y globales .

Cuando la JVM realiza una llamada JNI a código nativo, configura un registro para realizar un seguimiento de todos los objetos creados durante la llamada. Cualquier objeto creado durante la llamada nativa (es decir, devuelto desde una función de interfaz JNI) se agrega a este registro. Las referencias a dichos objetos se conocen como referencias locales . Cuando el método nativo vuelve a la JVM, se destruyen todas las referencias locales creadas durante la llamada al método nativo. Si está haciendo llamadas a la JVM durante una llamada al método nativo, la referencia local seguirá activa cuando el control vuelva al método nativo. Si la JVM invocada desde el código nativo realiza otra llamada al código nativo, se crea un nuevo registro de referencias locales y se aplican las mismas reglas.

(De hecho, puede implementar su propio ejecutable JVM (es decir, java.exe) utilizando la interfaz JNI, creando un JVM (recibiendo así un puntero JNIEnv *), buscando la clase dada en la línea de comandos. e invocando el método main () en él.)

Todas las referencias devueltas por los métodos de interfaz JNI son locales . Esto significa que, en circunstancias normales, no es necesario desasignar manualmente las referencias devueltas por los métodos JNI, ya que se destruyen al regresar a la JVM. A veces aún desea destruirlos "de forma prematura", por ejemplo, cuando tiene muchas referencias locales que desea eliminar antes de regresar a la JVM.

Las referencias globales se crean (a partir de referencias locales) mediante el uso de NewGlobalRef (). Se agregan a un registro especial y deben ser desasignados manualmente. Las referencias globales solo se utilizan para el objeto Java al que el código nativo debe contener una referencia a través de múltiples llamadas JNI, por ejemplo, si tiene eventos desencadenantes de código nativo que deberían propagarse a Java. En ese caso, el código JNI debe almacenar una referencia a un objeto Java que debe recibir el evento.

Espero que esto aclare un poco el problema de la administración de la memoria.

Re: " Una pregunta separada, pero relacionada "; no es necesario que libere manualmente jclass, jfieldID y jmethodID cuando los use en " local " contexto. Cualquier referencia de objeto real que obtenga (no jclass, jfieldID, jmethodID) debe publicarse con DeleteLocalRef.

El GC recopilaría su instancia, pero no liberará automáticamente la memoria de pila no java asignada en el código nativo. Debería tener un método explícito en su clase para liberar la instancia de c_object.

Este es uno de los casos en los que recomiendo usar un finalizador para verificar si c_object se ha liberado y lanzarlo, registrando un mensaje si no lo está.

Una técnica útil es crear una instancia de Throwable en el constructor de la clase Java y almacenarla en un campo (o simplemente inicializar el campo en línea). Si el finalizador detecta que la clase no se ha eliminado correctamente, imprimirá el seguimiento de la pila, señalando la pila de asignación.

Una sugerencia es evitar hacer JNI directo e ir con gluegen o Swig (ambos generan código y pueden vincularse estáticamente).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top