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:


Ozito