Referencias Locales y Globales
Hasta ahora, hemos utilizado tipos de datos como jobject, jclass, y jstring para denotar referencias a objetos Java. Sin embargo, el JNI crea referencias para todos los argumentos pasados a los métodos nativos, así como de los objetos devueltos desde funciones JNI.
Las referencias sirven para evitar que los objetos Java sean recolectados por el recolector de basura. Por defecto, el JNI crea referencias locales porque éstas aseguran que la Máquina Virtual pueda liberar eventualmente los objetos Java. Las referencias locales se vuelven inválidas cuando la ejecución del programa retorna desde el método nativo en el que se creó. Por lo tanto, un método nativo no debería almacencer una referencia local y esperar utilizarla en llamadas subsecuentes.
Por ejemplo, el siguiente programa, que es una variación del método nativo de FieldAccess.c, erróneamente captura el ID del campo Java para no tener que buscarlo repetidamente basándose en el nombre y la firma en cada llamada.
/* This code is illegal */
static jclass cls = 0;
static jfieldID fld;
JNIEXPORT void JNICALL
Java_FieldAccess_accessFields(JNIEnv *env, jobject obj)
{
...
if (cls == 0) {
cls = (*env)->GetObjectClass(env, obj);
if (cls == 0)
... /* error */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
}
... /* access the field using cls and fid */
}
Este código es ilegal porque la referencia local devuelta desde GetObjectClass es sólo válida antes de que retorne el método nativo. Cuando una aplicación Java llama al método nativo
Java_FieldAccess_accessField una segunda vez, el método nativo trata de utilizar una referencia no válida. Esto acabará en un resultado erróneo o un cuelgue de la Máquina Virtual.
Se puede solucionar este problema creando una referencia global. Una referencia global permanecerá válida hasta que se liberé explícitamente. El siguiente código reescribe el programa anterior y utiliza correctamente la referencia global para capturar el ID del objeto.
/* This code is OK */
static jclass cls = 0;
static jfieldID fld;
JNIEXPORT void JNICALL
Java_FieldAccess_accessFields(JNIEnv *env, jobject obj)
{
...
if (cls == 0) {
jclass cls1 = (*env)->GetObjectClass(env, obj);
if (cls1 == 0)
... /* error */
cls = (*env)->NewGlobalRef(env, cls1);
if (cls == 0)
... /* error */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
}
... /* access the field using cls and fid */
}
Una referencia global evita que la Maquina Virtual descargue la clase Java, y por lo tanto también asegura que el ID del campo permanezca válido, como se explicó en Acceder a Campos Java. Sin Embargo, el código nativo debe llamar a DeleteGlobalRefs cuando no necesite acceder más a la referencia global. De otro modo, la Máquina Virtual nunca descargará el objeto correspondiente.
En la mayoría de los casos, los programadores nativos relegan en la VM la liberación de las referencias locales después de retornar del método nativo. Sin embargo, en ciertas situaciones, el código nativo podría necesitar llamar a la función DeleteLocalRef para borrar explícitamente una referencia local. Estas situaciones son.
- Podríamos saber que estamos manteniendo la única refencia a un objeto Java de gran tamaño y no queremos esperar hasta que el método nativo retorne antes de que el recolector de basura reclame ese objeto. Por ejemplo, en el siguiente fragmento de código, el colector de basura podría liberar el objeto Java referenciado por lref cuando está dentro de lengthyComputation.
lref = ... /* a large Java object */
... /* last use of lref */
(*env)->DeleteLocalRef(env, lref);
lengthyComputation(); /* may take some time */
return; /* all local refs will now be freed */
}
- Podríamos necesitar crear un gran número de referencias locales en una sóla llamada a un método nativo. Esto podría resultar en una sobrecarga en la tabla de referencias locales del JNI. Es una buena idea borrar aquellas referencias locales que no se van a necesitar. Por ejemplo, en el siguiente fragmento de código, el código nativo itera a través de un array potencialmente grande arr que contiene strings Java. Después de cada iteración, el programa puede liberar la referencia local del elemento string.
for(i = 0; i < len; i++) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* processes jstr */
(*env)->DeleteLocalRef(env, jstr); /* no longer needs jstr */
}