java - metodos - Administración de memoria JNI usando la API de Invocación
jni java ejemplos (4)
Cuando estoy construyendo un objeto java usando métodos JNI, para pasarlo como un parámetro a un método java invocando usando la API de invocación JNI, ¿cómo administro su memoria?
Aquí 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 finaliza con el objeto Java, ya no necesito el objeto C.
Estoy creando el objeto Java de esa manera (se ha eliminado el error para mayor claridad):
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 la instance
, ¿qué hago con eso? Idealmente, me gustaría dejar la recolección de basura en la VM; cuando está hecho con una instance
, sería fantástico si también llamara a c_object_destroy()
en el puntero que le proporcioné. es 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 de JNI es frustrantemente vago (a mi juicio) sobre el tema de la gestión adecuada de la memoria.
La especificación de JNI cubre el problema de quién "posee" los objetos de Java creados en los métodos de JNI aquí . Necesita distinguir entre referencias locales y globales .
Cuando la JVM realiza una llamada JNI al 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 tales 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 vuelve a hacer llamadas en la JVM durante una llamada a un método nativo, la referencia local seguirá activa cuando el control regrese al método nativo. Si la JVM invocada desde el código nativo vuelve a hacer 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 archivo ejecutable JVM (es decir, java.exe) utilizando la interfaz JNI, creando una JVM (recibiendo un puntero JNIEnv *), buscando la clase dada en la línea de comando e invocando el método main () en él)
Todas las referencias devueltas desde los métodos de interfaz JNI son locales . Esto significa que, en circunstancias normales, no es necesario desasignar manualmente las referencias mediante métodos JNI, ya que se destruyen al volver a la JVM. A veces, todavía desea destruirlos "prematuramente", por ejemplo, cuando tiene muchas referencias locales que desea eliminar antes de volver a la JVM.
Las referencias globales se crean (a partir de referencias locales) utilizando NewGlobalRef (). Se agregan a un registro especial y deben ser desasignados manualmente. Las referencias globales solo se utilizan para el objeto Java que el código nativo necesita para 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 nuevamente a Java. En ese caso, el código JNI necesita almacenar una referencia a un objeto Java que recibirá el evento.
Espero que esto aclare un poco el problema de administración de memoria.
Re: "Una pregunta separada pero relacionada" ... no necesita liberar manualmente jclass, jfieldID y jmethodID cuando los usa en un contexto "local". Cualquier referencia de objeto real que obtenga (no jclass, jfieldID, jmethodID) se debe liberar con DeleteLocalRef.
El GC recolectaría su instancia, pero no liberará automáticamente la memoria del montón no-java asignada en el código nativo. Debería tener un método explícito en su clase para liberar la instancia c_object.
Este es uno de los casos en los que recomendaría usar un finalizador para verificar si c_object se ha liberado y liberarlo, registrando un mensaje si no es así.
Una técnica útil es crear una instancia 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á la pila de la pila, identificando la pila de asignación.
Una sugerencia es evitar hacer JNI directo e ir con gluegen o Swig (ambos generan código y pueden estar vinculados estáticamente).
Hay un par de estrategias para reclamar recursos nativos (objetos, descriptores de archivos, etc.)
Invoca un método JNI durante finalize () que libera el recurso. Algunas personas recomiendan no finalizar la implementación , y básicamente no puedes estar seguro de que tu recurso nativo sea liberado alguna vez. Para recursos como la memoria esto probablemente no sea un problema, pero si tiene un archivo, por ejemplo, que debe enjuagarse en un momento predecible, finalize () probablemente no sea una buena idea.
Invocar manualmente un método de limpieza. Esto es útil si tiene un punto en el tiempo donde sabe que el recurso debe ser limpiado. Usé 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, tuve que asegurarme de que el objeto estaba realmente desasignado antes de intentar descargar la DLL. Usando solo finalize (), no habría obtenido 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. (Probablemente necesites un mapa canónico de WeakReferences para rastrear qué objetos necesitan tener invocado su método de limpieza).
Supuestamente, PhantomReference también se puede usar para resolver este problema, pero no estoy seguro de cómo funcionaría esa solución.
De hecho, tengo que estar en desacuerdo con usted en la documentación de JNI. Considero que la especificación de JNI es excepcionalmente clara en 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 elaboradas.