2008-10-18 13 views
5

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 manejo su ¿memoria?Administración de memoria JNI usando la API de invocación

Esto es lo que estoy trabajando con:

Tengo un objeto de 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 como tal (comprobación de errores elided 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); 

Así que, ahora que he terminado con instance, ¿qué hago con ella? Idealmente, me gustaría dejar la recolección de basura en la VM; cuando se hace con instance sería fantástico si también llamara 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, por ejemplo, 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.

Respuesta

5

Hay un par de estrategias para la recuperación de los recursos nativos (objetos, descriptores de archivos, etc.)

  1. invocar un método JNI durante finalize() que libera el recurso. Algunas personas recommend against implementing finalize, 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.

  2. Invoque 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.)

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

En realidad, no estoy de acuerdo con usted en la documentación de JNI. Encuentro que el JNI specification es excepcionalmente claro en la mayoría de los temas importantes, incluso si las secciones sobre la administración de referencias locales y globales podrían haber sido más elaboradas.

+0

Son esos aspectos específicos que necesitaba aclarar, lamentablemente :) No sé si alguna vez ha intentado ejecutar una JVM en valgrind (una herramienta de comprobación de memoria de Linux) pero tiene tantas operaciones sospechosas que es imposible leer el salida, así que claro documento sobre cómo funcionan las asignaciones de memoria es fundamental. –

+0

En realidad, tengo. Intenté ejecutar Eclipse en Valgrind y fallé: http://stackoverflow.com/questions/189284/running-eclipse-under-valgrind. – JesperE

0

El GC recolectarí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 c_object.

Este es uno de los casos en los que recomendaría usar un finalizador que verifique si c_object ha sido liberado y lo liberé, 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 se pueden vincular estáticamente).

+0

He investigado eso, pero nuestra aplicación se compone principalmente de funcionalidades vinculadas estáticamente, lo que significa que esas herramientas, que principalmente parecen tratar con la invocación del código C en bibliotecas compartidas, en lugar de llamar a código Java desde C - no encajaba en la solución. Gracias, sin embargo. –

+0

He actualizado la respuesta. He usado swig en un proyecto estáticamente vinculado. Gluegen se ve mejor (se supone que funciona casi sin anotaciones), pero es menos maduro. – ddimitrov

1

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

+0

Para ser específico, ¿eso es cierto incluso cuando la pila local no está en una pila de llamadas de VM? Estoy llamando a la máquina virtual desde esta pila, y no al revés ... –

+0

Todavía está conectado a la máquina virtual cada vez que tiene un puntero JNIEnv *. Siempre y cuando no estés tratando de aferrarte a la jclass, jfieldID, jmethodID después de que la VM (o el cargador de clases) desaparezca, deberías estar bien. –

10

La especificación JNI cubre el problema de quién "posee" los objetos Java creados en los métodos JNI here. Debe distinguir entre local y referencias 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) usando la interfaz JNI, creando una JVM (recibiendo un puntero JNIEnv *), buscando la clase dada en la línea de comando e invocar 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 usan para objetos 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 se deben propagar 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.

Cuestiones relacionadas