views:

2087

answers:

4

When I'm building a java object using JNI methods, in order to pass it in as a parameter to a java method I'm invoking using the JNI invocation API, how do I manage its memory?

Here's what I am working with:

I have a C object that has a destructor method that is more complex that free(). This C object is to be associated with a Java object, and once the application is finished with the Java object, I have no more need for the C object.

I am creating the Java object like so (error checking elided for clarity):

c_object = c_object_create ();
class = (*env)->FindClass (env, "my.class.name");
constructor = (*env)->GetMethodID (env, class, "<init>", "(J)V");
instance = (*env)->NewObject (env, class, constructor, (jlong) c_object);

method = (*env)->GetMethodID (env, other_class, "doSomeWork", "(Lmy.class.name)V");
(*env)->CallVoidMethod (env, other_class, method, instance);

So, now that I'm done with instance, what do I do with it? Ideally, I'd like to leave the garbage collection up to the VM; when it's done with instance it would be fantastic if it also called c_object_destroy() on the pointer I provided to it. Is this possible?

A separate, but related question has to do with the scope of Java entities that I create in a method like this; do I have to manually release, say, class, constructor, or method above? The JNI doc is frustratingly vague (in my judgement) on the subject of proper memory management.

A: 

The GC would collect your instance, but it will not automatically release the non-java heap memory allocated in the native code. You should have explicit method in your class to release the c_object instance.

This is one of the cases where I'd recommend using a finalizer checking if c_object has been released and release it, logging a message if it's not.

A useful technique is to create a Throwable instance in the Java class constructor and store it in a field (or just initialize the field inline). If the finalizer detects that the class has not been properly disposed it would print the stacktrace, pinpointing the allocation stack.

A suggestion is to avoid doing straight JNI and go with gluegen or Swig (both generate code and can be statically linked).

ddimitrov
I looked into those, but our application is primarily made up of statically-linked functionality, which means that those tools -- which mainly seem to deal with the calling of C code in shared libraries, as opposed to calling Java code from C -- didn't fit into the solution. Thanks, nonetheless.
Chris R
I've updated the answer. I've used swig in a statically linked project. Gluegen looks better (supposed to work almost without annotations), but is less mature.
ddimitrov
+2  A: 

There are a couple of strategies for reclaiming native resources (objects, file descriptors, etc.)

  1. Invoke a JNI method during finalize() which frees the resource. Some people recommend against implementing finalize, and basically you can't really be sure that your native resource is ever freed. For resources such as memory this is probably not a problem, but if you have a file for example which needs to be flushed at a predictable time, finalize() probably not a good idea.

  2. Manually invoke a cleanup method. This is useful if you have a point in time where you know that the resource must be cleaned up. I used this method when I had a resource which had to be deallocated before unloading a DLL in the JNI code. In order to allow the DLL to later be reloaded, I had to be sure that the object was really deallocated before attempting to unload the DLL. Using only finalize(), I would not have gotten this guaranteed. This can be combined with (1) to allow the resource to be allocated either during finalize() or at the manually called cleanup method. (You probably need a canonical map of WeakReferences to track which objects needs to have their cleanup method invoked.)

  3. Supposedly the PhantomReference can be used to solve this problem as well, but I'm not sure exactly how such a solution would work.

Actually, I have to disagree with you on the JNI documentation. I find the JNI specification exceptionally clear on most of the important issues, even if the sections on managing local and global references could have been more elaborated.

JesperE
It's those specific aspects that I needed clarified, regrettably :) I don't know if you've ever tried to run a JVM under valgrind (a linux memory checking tool) but it's got so many suspect operations that it's impossible to read the output, so clear doc on how memory allocations work is critical.
Chris R
Actually, I have. I tried running Eclipse under Valgrind and failed: http://stackoverflow.com/questions/189284/running-eclipse-under-valgrind.
JesperE
A: 

Re: "A separate, but related question"... you do not need to manually release jclass, jfieldID and jmethodID when you use them in a "local" context. Any actual object references you obtain (not jclass, jfieldID, jmethodID) should be released with DeleteLocalRef.

Adam Mitz
To be specific, is that true even when the local stack is not on a VM call stack? I'm _calling_ the VM from this stack, not the other way around...
Chris R
You're still attached to the VM any time you have a JNIEnv* pointer. As long as you're not trying to hold on to the jclass, jfieldID, jmethodID after the VM (or classloader) goes away, you should be fine.
Adam Mitz
+1  A: 

The JNI spec covers the issue of who "owns" Java objects created in JNI methods here. You need to distinguish between local and global references.

When the JVM makes a JNI call out to native code, it sets up a registry to keep track of all objects created during the call. Any object created during the native call (i.e. returned from a JNI interface function) are added to this registry. References to such objects are known as local references. When the native method returns to the JVM, all local references created during the native method call are destroyed. If you're making calls back into the JVM during a native method call, the local reference will still be alive when control returns back to the native method. If the JVM invoked from native code makes another call back into the native code, a new registry of local references is created, and the same rules apply.

(In fact, you can implement you're own JVM executable (i.e. java.exe) using the JNI interface, by creating a JVM (thereby receiving a JNIEnv * pointer), looking up the class given on the command line, and invoking the main() method on it.)

All references returned from JNI interface methods are local. This means that under normal circumstances you do not need to manually deallocate references return by JNI methods, since they are destroyed when returning to the JVM. Sometimes you still want to destroy them "prematurely", for example when you lots of local references which you want to delete before returning to the JVM.

Global references are created (from local references) by using the NewGlobalRef(). They are added to a special registry and have to be deallocated manually. Global references are only used for Java object which the native code needs to hold a reference to across multiple JNI calls, for example if you have native code triggering events which should be propagated back to Java. In that case, the JNI code needs to store a reference to a Java object which is to receive the event.

Hope this clarifies the memory management issue a little bit.

JesperE