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;
}
(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.
Since Cleaner
s 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));
}
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.
}
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.