Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the rules for JNI local references returned to a program that embeds a Java VM?

The JNI docs describe the rules for resource management of objects returned from the JNI. Here's a quote that gives a good overview:

The JNI divides object references used by the native code into two categories: local and global references. Local references are valid for the duration of a native method call, and are automatically freed after the native method returns. Global references remain valid until they are explicitly freed.

Objects are passed to native methods as local references. All Java objects returned by JNI functions are local references. The JNI allows the programmer to create global references from local references. JNI functions that expect Java objects accept both global and local references. A native method may return a local or global reference to the VM as its result.

This documentation is obviously geared towards using the JNI to implement methods in native code but the JNI can also be used for embedding. What are the rules for embedding? When the 'native method call' that a JNI function returns to is an enclosing program which is embedding a VM, the 'native method call', i.e. the program, will never return to the VM. What are the rules in that case? Can a program that embeds the JNI tell the JNI that it can free a previously returned object?

Edit:

Here's an example of code where I'm not sure how to handle an object returned from the JNI based on the docs. TestKlass.java defines a simple Java class. run.c starts an embedded Java VM and loads the TestKlass class using the JNI and then runs the TestKlass constructor to get a jobject instance of TestKlass. What are the resource management rules for the returned jobject? When will the Java VM assume that it can safely release the object?

The code starts the Java VM with Xcheck:jni and the VM does not print any errors, but that doesn't guarantee that no errors exist in this code. If there are errors in this example, how could I have detected them?

TestKlass.java

public class TestKlass {
    public TestKlass() {
                System.out.println("Java: TestKlass::TestKlass()");
    }
}

run.c

#include <jni.h>
#include <stdlib.h>

JNIEnv* create_vm(JavaVM** jvm)
{
    JNIEnv* env;
    JavaVMInitArgs vm_args;

    // For this example, TestKlass.java and run.c are assumed to live in and
    //     be compiled in the same directory, so '.' is added to the Java
    //     path.
    char opts0[] = "-Djava.class.path=.";
    char opts1[] = "-Xcheck:jni";
    JavaVMOption opts[2];
    opts[0].optionString = opts0;
    opts[1].optionString = opts1;

    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 2;
    vm_args.options = opts;
    vm_args.ignoreUnrecognized = 0;

    jint r = JNI_CreateJavaVM(jvm, (void**)&env, &vm_args);
    if (r < 0 || !env) {
        printf("Unable to Launch JVM %d\n", r);
        abort();
    }
    printf("Launched JVM! :)\n");

    return env;
}

int main(int argc, char **argv)
{
    JavaVM *jvm;
    JNIEnv *env;
    env = create_vm(&jvm);
    if(env == NULL)
        return 1;

    jclass cls = (*env)->FindClass(env, "TestKlass");
    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
    jobject jobj = (*env)->NewObject(env, cls, mid);

    // (Assume that arbitrary JNI calls will be made after this point, the
    //     program will persist for a long time, and returned Java objects may
    //     be large.)
    // What are the rules for negotiating management of jobj with the Java VM?
    // Does the answer change if the object was returned from a
    //     non-constructor function?
    // Is there any way to use the JNI to tell the Java VM either that jobj
    //     can be freed here or that it must not be freed here?
    // Would DeleteLocalRef() be helpful here?

    // ...

    (*jvm)->DestroyJavaVM(jvm);
}

output

Launched JVM! :)
Java: TestKlass::TestKlass()

I know C but I haven't used Java in a long time. I'm mostly working from the JNI docs. If I'm missing something obvious, please point it out in an answer or comment.

like image 313
Praxeolitic Avatar asked Oct 20 '25 03:10

Praxeolitic


1 Answers

As you say, you are not in a native method so there will be no cleaning up upon "return". Your question is about cleaning well before return.

To free a local reference, you have two choices:

  • DeleteLocalRef for one reference.
  • PushLocalFrame/PopLocalFrame for a group of references.

(I suspect PushLocalFrame/PopLocalFrame is how the clean up of a native method is done.)

Example:

TestKlass.java

public class TestKlass {
    public TestKlass() {
        System.out.println("Java: TestKlass::TestKlass()");
    }

    public void finalize() {
        System.out.println("Java: TestKlass::finalize()");
    }

    public static void force_gc() {
        System.out.println("Java: TestKlass::force_gc()");

        System.gc();
        System.runFinalization();
    }
}

run.c

#include <jni.h>
#include <stdlib.h>

JNIEnv* create_vm(JavaVM** jvm)
{
    JNIEnv* env;
    JavaVMInitArgs vm_args;

   // For this example, TestKlass.java and run.c are assumed to live in and
    //     be compiled the same directory, so '.' is added to the Java path.
    char opts0[] = "-Djava.class.path=.";
    char opts1[] = "-Xcheck:jni";
    JavaVMOption opts[2];
    opts[0].optionString = opts0;
    opts[1].optionString = opts1;

    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 2;
    vm_args.options = opts;
    vm_args.ignoreUnrecognized = 0;

    jint r = JNI_CreateJavaVM(jvm, (void**)&env, &vm_args);
    if (r < 0 || !env) {
        printf("Unable to Launch JVM %d\n", r);
        abort();
    }
    printf("Launched JVM! :)\n");

    return env;
}

int main(int argc, char **argv)
{
    JavaVM *jvm;
    JNIEnv *env;
    env = create_vm(&jvm);
    if(env == NULL)
        return 1;

    jclass cls = (*env)->FindClass(env, "TestKlass");
    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
    jobject jobj = (*env)->NewObject(env, cls, mid);

    (*env)->DeleteLocalRef(env, jobj);
    jmethodID mid2 = (*env)->GetStaticMethodID(env, cls, "force_gc", "()V");
    (*env)->CallStaticVoidMethod(env, cls, mid2);

    (*jvm)->DestroyJavaVM(jvm);
}

output

Launched JVM! :)
Java: TestKlass::TestKlass()
Java: TestKlass::force_gc()
Java: TestKlass::finalize()

Removing either the call to DeleteLocalRef() or the call to force_gc() prevents finalize() from running.

like image 58
Tom Blodget Avatar answered Oct 21 '25 15:10

Tom Blodget



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!