I need to clean up resources allocated by a JNI call. It's easy to do it by overriding Object.finalize() method. Since this method is deprecated starting with Java 9, I'm trying to achieve the same thing using the new java.lang.ref.Cleaner class.
Here is the code for calling ToBeCleaned.cleanUp method before the instance is garbage collected:
import java.lang.ref.Cleaner;
import java.lang.ref.WeakReference;
public class ToBeCleaned {
private static Cleaner cleaner = Cleaner.create();
public ToBeCleaned() {
cleaner.register(this, new CleanRunnable(this));
}
void cleanUp () {
// do cleanup
}
static class CleanRunnable implements Runnable {
// It has to be weak reference, otherwise ToBeCleaned instance
// would never be eligible for GC
private WeakReference<ToBeCleaned> toBeCleanedWeakReference;
CleanRunnable(ToBeCleaned toBeCleaned) {
this.toBeCleanedWeakReference = new WeakReference<>(toBeCleaned);
}
@Override
public void run() {
toBeCleanedWeakReference.get().cleanUp();
}
}
}
My question: Is this the right approach?
Your approach has a flaw. The "cleaning action" must not depend on having access to the instance registered with the Cleaner.
In short, the call to toBeCleanedWeakReference.get() in your code will return null since the ToBeCleaned instance will have been, at least from our perspective, garbage collected by that point.
The correct approach is to somehow reference the resource that needs to be cleaned up without "going through" the ToBeCleaned instance. Typically this means either:
Making the cleaning action and the resource the same object (distinct from the object registered with the Cleaner). The documentation of Cleaner shows an example of this approach.
Passing a reference to the resource, but not the object registered with the Cleaner, to the cleaning action when instantiating it. Here's an example:
public class ToBeCleaned implements AutoCloseable {
// documentation suggests you should preferably have one
// Cleaner instance per library
private static final Cleaner CLEANER = ...;
private final Cleaner.Cleanable cleanable;
private final SomeResource resource;
public ToBeCleaned() {
resource = ...;
cleanable = CLEANER.register(this, new CleaningAction(resource));
}
@Override
public void close() {
cleanable.clean();
}
private static class CleaningAction implements Runnable {
private final SomeResource resource;
CleaningAction(SomeResource resource) {
this.resource = resource;
}
@Override
public void run() {
// clean up 'resource'
}
}
}
Both examples implement AutoCloseable. That gives users of your API the ability to release the resources on-demand rather than waiting for the garbage collector to kick in (which makes the Cleaner more of a "back up").