JNI memory management using the Invocation API

11,323

Solution 1

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.

Solution 2

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.

Share:
11,323
Chris R
Author by

Chris R

I'm a software developer and inveterate geek (like many here, I suspect). For work I use so many tools I usually can't remember them all, but recently they've been heavily Python/Java. C, Java/J2EE, various scripting and release engineering tools figure heavily in the list as well.

Updated on June 18, 2022

Comments

  • Chris R
    Chris R almost 2 years

    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.

  • Chris R
    Chris R over 15 years
    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
    Chris R over 15 years
    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.
  • JesperE
    JesperE over 15 years
    Actually, I have. I tried running Eclipse under Valgrind and failed: stackoverflow.com/questions/189284/….
  • ddimitrov
    ddimitrov over 15 years
    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.