Forcing the GC to collect JNI proxy objects

2020-07-27 03:55发布

问题:

While I do my best to clean JNI objects to free native memory in the end of the usage, there are still some that hang around for a long time, wasting system native memory.

Is there any way to force the GC to give priority in collection of these JNI proxies?

I mean is there a way to cause GC to concentrate on a particular kind of object, namely the JNI proxies?

Thanks.

回答1:

If you are talking about memory (and, by extension, handles) allocated within native code, it is outside of the purview of the JVM's garbage collector - there is nothing it can do about it so you are on your own. If you don't release the memory in the native code when you are done, it will leak.

If you are referring to the Java objects through which you access the native code, they are perfectly normal objects which will be collected when they become unreachable. Note that if you pin Java objects in native code (e.g. with GetByteArrayElements you must also release them (e.g. with ReleaseByteArrayElements).

If your native code must release resources before you let the Java object go, the Java object should have a dispose method of some sort which when called will release the native resources and invalidate the Java object from further use. Simply call the dispose method and let the object reference go.

Once last thing, I am aware of no way to unload a native library once loaded.



回答2:

There's no way to make the GC "focus" on certain types of objects. I assume you clean up in the finalizer, and the finalizer is run when:

  • The object no longer reachable.
  • The GC decides to clean up the generation the JNI proxy is in.

This means that, in order to clean up the resources as fast as possible, you want to:

  • Reduce the scope of the references, so your program doesn't cling to them for an unnecessarily long time. Also, garbage collections are run more seldom for old objects, so there's a twofold reason to make sure they live as short a time as possible.
  • Add a manual clean-up method the client code can call once it's finished with a JNI proxy - don't just let the references dissipate and wait for the finalizer to be run.

Example:

class NativeResource {
    private static native long allocate();
    private static native void release(long handle);
    private final long handle;
    private boolean closed = false;
    public NativeResource(){
        handle = allocate();
    }
    /** Deallocates the native resources associated with this proxy. */
    public void close() { 
        if (closed) throw new IllegalStateException("Already closed");
        release(handle); 
        closed = true;
    }
    protected void finalize() throws Throwable {
        try { 
            if (!closed) release(handle);
        } finally {
            super.finalize();
        }
    }
}

// Usage:
NativeResource nr = new NativeResource();
try {
    // Use the resource for something
} finally {
    nr.close(); // Make sure resource is closed even after exceptions
}


回答3:

Your mental model of GC is wrong. GC doesn't collect objects and then free them.

GC collects live objects. All other memory is then defined as free.

There are wrinkles to this for objects with finalizers, and optimizations for objects that may be allocated on the stack etc., but this is the right mental model to have.

Global references (the persistent form of reference available via JNI) act as roots for objects. The GC starts from roots and recursively follows all links when finding live objects. If the global reference is deleted, then it will stop keeping the referenced object alive. The GC may then reclaim the memory used by the object, but only if there aren't any other references, and only during a subsequent collection. There is no general way to reclaim memory for any specific subset of objects.