Returning local reference created by JNI from a na

2019-06-02 19:45发布

问题:

JNI reference says that

"Local references are valid for the duration of a native method call. They are freed automatically after the native method returns.

Source: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#global_local

I'm kind of lost here. According to above, I must explicitly call NewGlobalRef and pass object returned from a call to NewObject. I tried this and it seems that when a GC kicks in, it does not collect my references (like something still holds onto them). Consider the following project: Main.java:

package lv.example;

import java.io.IOException;
import java.util.ArrayList;

class Main {

    public static void main(String[] args) {
        ArrayList<Object> store = new ArrayList<Object>();
            while(true) {
                Object d = null;
                try {
                    int c = System.in.read();
                    d = Dummy.getWeakGlobalRef();
                    if(c == 'c')
                        store.clear();
                    store.add(d);
                    System.out.println(d);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

    }

}

Dummy.java:

package lv.example;

class Dummy {

    static {

        System.loadLibrary("dummy");
    }
       native static Dummy getLocalRef();
       native static Dummy getGlobalRef();
       native static Dummy getWeakGlobalRef();

       @Override
       protected void finalize() throws Throwable {
            System.out.println("Finalized");
       }

}

libdummy.so contains implementation of native methods:

JNIEXPORT jobject JNICALL Java_lv_example_Dummy_getLocalRef (JNIEnv *env, jclass cls) {
    jmethodID id = env->GetMethodID(cls, "<init>", "()V");
    return env->NewObject(cls, id);
}


JNIEXPORT jobject JNICALL Java_lv_example_Dummy_getGlobalRef (JNIEnv *env, jclass cls) {

    jmethodID id = env->GetMethodID(cls, "<init>", "()V");
    return env->NewGlobalRef(env->NewObject(cls, id));
}


JNIEXPORT jobject JNICALL Java_lv_example_Dummy_getWeakGlobalRef (JNIEnv *env, jclass cls) {
    jmethodID id = env->GetMethodID(cls, "<init>", "()V");
    return env->NewWeakGlobalRef(env->NewObject(cls, id));
}

Behaviour that main loop exhibits seems strange to me: 1) When I call getWeakGlobalRef or getLocalRef and clear ArrayList, GC seems to collect all the Dummy objects I've created. 2) When I call getGlobalRef, no objects get collected regardless if I clear ArrayList or not.l

I'm kind of confused here, does that mean that I can return local references from native methods back to Java code?

回答1:

The JVM needs to know whether the native code is keeping its own references to the objects. That is if the object were deleted, is it possible for the C/C++ code to still try access it (probably causing a segfault). The objects with global references are not garbage collected because the native code has indicated that it might still need them. The objects with local references can be garbage collected because the function where the references were created has already returned to java. The objects with weak global references can be garbage collected because the native code has indicated it will use IsSameObject to check if the object with the weak reference has been garbage collected so that it will behave correctly using the object only if it has not been garbage collected.

re:

So this means, that New*Ref is for pinning for native code only? Does JVM track a "local reference" to the point where I assign it to a instance of Java variable?

Local references are tracked until the return to java. Assigning it to a java variable doesn't remove the reference although it would independently prevent garbage collection of that object. The returned value would have a reference on the stack preventing garbage collection until it could be assigned to a java variable so there is no need for a returned object to have a global reference unless the native code might access that same object after the return. Without the local reference the JVM might garbage collect the object while the JNI function were executing since the garbage collector runs in another thread. Normally one doesn't need to explicitly call NewRef/DeleteRef as

All Java objects passed to the native method (including those that are returned as the results of JNI function calls) are automatically added to the registry(ie given a local reference).

(quoted from https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#implementing_local_references)

Cases you might need to do it.

The native function spawns a thread that continues using the object after control is returned to java in the original thread.

The native function stores a copy of the reference in some global variable for use in subsequent calls from java.

You want to explicitly delete references while the function is running to conserve memory during a function that might take a while.



回答2:

Yes. A JNI local reference refers to an object; You can return a reference to that object. Back on the Java side, "local reference" doesn't have any meaning.

The garbage collector works by starting with a set of root "live" references and marking any object reachable from them as "live". Every other object is subject to deletion.

JNI's local and global references are kept in the set of root references. Local references are automatically removed when the native method that created them returns. You can, and sometimes should, delete local references before that. Global references are explicitly created and removed by native code at any time.