Search code examples
javac++performancememory-managementjava-native-interface

Alternative for deprecated finalize method in java for JNI


I have a requirement to implement a bidirectional communication between C++ and Java where i use JNI. Most of the issues are solved by using JNI but i am worried more where i pass a pointer reference of a variable in heap memory of C++ to Java. I use the new keyword to allocate the reference in the heap memory and pass it to Java for future references to that particular variable. So, I've to use delete to deallocate the variable from the heap memory of C++. Usually in Java this is achieved by using overriding finalize() method so the garbage collector invokes this method when deleting an object, but i see that finalize is deprecated from Java 9. Is there any other way to achieve the required functionality?

C++ Code (Passing C++ Object reference to Java):

jobject Helper::toJavaHistory(const History& history) {

    auto* historyPtr = new History(history);
    jobject historyObject = getEnv()->NewObject(getHistoryClass(), getMethodID("History","<init>"),reinterpret_cast<jlong>(historyPtr));
    return historyObject;
}

Java Code:

public class History {
    
    public long historyPointer;

    public History(long historyPointer){
        this.historyPointer = historyPointer;
    }
    native void deleteHistory(long historyPtr);

}

JNI Native Implementation C++:

JNIEXPORT void JNICALL Java_com_example_core_History_deleteHistory
        (JNIEnv * env, jobject thisHistoryObject, jlong historyPtr) {
    auto* history = reinterpret_cast<History*>(historyPtr);
    delete history;
}

Solution

  • (I voted to reopen this question as the linked duplicate did not show a way to do this for JNI code)

    The Cleaner API introduced in Java 9 allows you to register Runnable objects when your object becomes unreachable and thus eligible for garbage collection.

    I will show two equivalent approaches that involve JNI methods. Both require you to associate a Cleaner with your History class:

    static Cleaner cleaner = cleaner.create();
    

    For you, the end goal is to call the static (!) deleteHistory method on the long holding the variable state.

    The long approach

    Since Cleaners want a Runnable, let us define one:

    static class HistoryCleaner implements Runnable {
      private long ptr;
      public HistoryCleaner(long ptr) { this.ptr = ptr; }
      public void run() {
        History.deleteHistory(this.ptr);
      }
    }
    

    Make sure that HistoryCleaner does not create any reference to your Cleaner object, as that causes a reference cycle and will prevent the object from being garbage-collected.

    For more advanced use cases, you can define the run method as native and implement it in c++ as

    JNIEXPORT void JNICALL Java_HistoryCleaner_run(JNIEnv *env, jobject thiz) { ... }
    

    Keep in mind that the thiz object is your HistoryCleaner, not the History object. The constructor can now look like this:

    public History(long historyPointer){
        this.historyPointer = historyPointer;
        this.cleanable = cleaner.register(this, new HistoryCleaner(historyPointer));
    }
    

    Short-hand

    You can use lambda syntax to skip creating a Runnable, but you must take care not to reference anything about the object. Thus, the constructor can become:

    public History(long historyPtr){
        this.historyPointer = historyPtr;
        this.cleanable = cleaner.register(this, () -> History.deleteHistory(historyPtr));
        // Note: historyPtr is a local variable so this does not keep a reference to the object.
    }
    

    A note on (Auto)Closable

    As the Cleaner documentation shows, the cleanable object returned by Cleaner#register is useful to implement the Closeable#close method:

    public void close() {
       cleanable.clean();
    }
    

    This allows you to use the java try-with-resources form to implicitly call deleteHistory for you OR call it automatically for you if you forget.