3

I'm wondering how safe it is to use a local reference created in a native method and returned by that method to the caller.

Here's a simple example:

jobject getAJObject(JNIEnv* jni) {
    jobject obj = jni->CallStaticVoidMethod(...); // java method that returns a jobject
    return obj;
}

void func(JNIEnv* jni) {
    jobject obj = getAJObject(jni);
    // Code that uses obj
    ...
}

I have tested this code and it does work fine, but I'm worried that it's not safe. My understanding from reading the JNI spec is local references are only valid in the stack frame it was created, and are cleaned up when the native method returns. Does this mean that obj can get garbage collected after getAJObject finishes, and while still on the native side without returning back to java?

This article indicates that this code is not safe: http://publib.boulder.ibm.com/infocenter/javasdk/v1r4m2/index.jsp?topic=%2Fcom.ibm.java.doc.diagnostics.142j9%2Fhtml%2Fhandlocref.html

However I still see examples of JNI code that do exactly this! Was hoping for some more clarification.

case12
  • 125
  • 9
  • 1
    Use `NewGlobalRef` on it, return it and then when finished use `DeleteGlobalRef` from the other function.. Otherwise you're accessing an object allocated on the stack of `getAJObject` from `func`.. resulting in UB. – Brandon Mar 08 '14 at 21:05
  • Was hoping this wasn't the answer. Can I define a jobject in func, and pass it as a parameter to getAObject and assign it in there? – case12 Mar 08 '14 at 21:12
  • You can't do that since the object is destroyed when it goes out of scope. That would still be the equivalent of returning it. So again, it will be undefined behaviour to do that (unless you make it a globalref as discussed earlier). It sounds like you are trying to work around creating a global reference.. Any particular reason why? – Brandon Mar 08 '14 at 21:20
  • There's a limit to global references, and you have to make sure to call DeleteGlobalRef. Which is fine but I need to DeleteGlobalRef in two places (a catch clause as well). It's a minor complaint, but if there was an alternative I would have preferred it. – case12 Mar 08 '14 at 21:38
  • Also, looking at the android source code, they seem to be doing the same thing. (Look in createBitmap) https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/jni/android/graphics/Graphics.cpp – case12 Mar 08 '14 at 21:39
  • http://android-developers.blogspot.ca/2011/11/jni-local-reference-changes-in-ics.html See the section that says: `A quick primer on JNI references`.. It also goes on to state that there was a time when local references could be used indefinitely. You may return localreferences only to Java code.. Passing it around on the C/C++ side of things requires a globalref. They could also be using a customized JVM in android. It is difficult to figure out when local-references really get invalidated but to be safe, use a global one when passing it around on the C or C++ side. – Brandon Mar 08 '14 at 22:03
  • @CantChooseUsernames: it is safe to return local references within one native call. As you mentioned, such reference can even be returned to Java. – Alex Cohn Mar 10 '14 at 04:43
  • @CantChooseUsernames: where you would need global references: if `getAJObject()` is called on one thread, but the result is used on another thread (threads). – Alex Cohn Mar 10 '14 at 04:51

1 Answers1

2

You can safely use getAJObject() from func(). All garbage collection and local reference management for the native object obj are frozen while the JNI call is under way. Let me address two different scenarios:

  1. If your code calls func() from a Java thread (i.e. if JVM can be found in the call stack), if there was a native Java method that called func() through some chain of calls, then this native method defines the scope of JNI local references frame.

  2. Alternatively, your code calls func() from a native thread, which needed to call jint AttachCurrentThread(JavaVM *vm, void **penv, void *args) or jint AttachCurrentThreadAsDaemon(JavaVM *vm, void **penv, void *args) to obtain JNIEnv* jni. In this case, the scope of local references, etc. remains until this thread calls jint DetachCurrentThread(JavaVM *vm).

Note that in case 2, if the thread dies without call to DetachCurrentThread(), your JVM will crash.

You can also manage local references scope manually, with jint PushLocalFrame(JNIEnv *env, jint capacity) and jobject PopLocalFrame(JNIEnv *env, jobject result).

Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
  • This is how I figured that it worked, is there documentation that specifically states this? Will something like this vary from implementation to implementation (I'm using android) The JNI spec says in one place "Local references are valid for the duration of a native method call", while also saying "local references are automatically freed after the native method returns to Java". (http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#global_local) – case12 Mar 09 '14 at 17:58
  • Would like to add that I found in the android reference that "If you attach a native thread with AttachCurrentThread, the code you are running will never automatically free local references until the thread detaches." [android documentation](http://developer.android.com/training/articles/perf-jni.html#local_and_global_references). You are indeed correct Alex! – case12 Mar 09 '14 at 18:28
  • Now I would just like to make sure that in Case 1 that you described, the GC won't run while still in the native code. This source states that they will get deleted (as linked by CantChooseUsernames): [http://android-developers.blogspot.ca/2011/11/jni-local-reference-changes-in-ics.html] "When a native method returns, all local references are automatically deleted." – case12 Mar 09 '14 at 19:31
  • Re: Case 1. This is exactly what *"Local references are valid for the duration of a native method call"* means! – Alex Cohn Mar 10 '14 at 04:45
  • So the function getAJObject() is not what they are referring to when they say "native method call"? Native method call specifically means the function that was called from Java? This is probably where all my confusion is originating if that's the case. – case12 Mar 10 '14 at 05:59
  • ***native method*** is a Java notion, see e.g. http://stackoverflow.com/a/6101528/192373 – Alex Cohn Mar 10 '14 at 17:51
  • I'm a bit confused about this still... IBM docs seem to explicitly say your Case 1 would be incorrect: http://publib.boulder.ibm.com/infocenter/javasdk/v1r4m2/index.jsp?topic=%2Fcom.ibm.java.doc.diagnostics.142j9%2Fhtml%2Fhandlocref.html but that doesn't make sense to me. – filipe Oct 12 '14 at 17:18
  • @filipe: your link only expands on my case 1. It addresses the question: what happens to the local reference and the objects referred from the local references _after_ the native call returnes? The answer is: sometimes, these objects may seem to survive, but they may get garbage collected unexpectedly. – Alex Cohn Oct 12 '14 at 20:48