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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With