I’m using Java 11. I have a class that uses the deprecated finalize method, and I want to start using ‘Cleaner’ instead.
This is the class I use:
public class TempFile2 extends File
{
private static final Cleaner cleaner = Cleaner.create();
public TempFile2(String pathname)
{
super(pathname);
cleaner.register(this, cleanAction());
}
private Runnable cleanAction() {
return () -> {
System.out.println("inside cleanAction");
if (this.exists())
{
System.out.println("deleting " + this.getName());
this.delete();
}
};
}
}
And using it like so:
public static void main(String[] args) throws IOException, InterruptedException {
String filePath = "<some_path>";
TempFile2 tempFile2 = new TempFile2(filePath);
tempFile2.createNewFile();
tempFile2 = null;
System.gc();
Thread.sleep(10);
}
Why is the cleanAction() method inside TempFile2 not called? (by the way, I know that System.gc() doesn’t guarantee GC to run, but I know that in my example it runs since I’m also monitoring with VisualVM and trigger GC from there, and verify it runs).
When I run the same except TempFile2 is using a finalize() method then the finalize() is called.
>Solution :
You’re doing exactly what the javadoc of Cleaner tells you not to do:
Note that the cleaning action must not refer to the object being
registered. If so, the object will not become phantom reachable and
the cleaning action will not be invoked automatically.
Also, it is not recommended to use things like lambdas or (anonymous) inner classes, because that will (or in the case of lambdas, could) retain a strong reference (which is what happens here because your lambda references this).
You cannot write a direct equivalent of your old finalizer. Either consider calling File#deleteOnExit() in the constructor (which might be too long a delay for long running programs), or register the path as a string or Path in your cleanup action, and do the cleanup action with that.
For example, something like this:
public class TempFile2 extends File {
private static final Cleaner cleaner = Cleaner.create();
public TempFile2(String pathname) {
super(pathname);
cleaner.register(this, new CleanupFileAction(this.toPath()));
}
private static class CleanupFileAction implements Runnable {
private final Path filePath;
private CleanupFileAction(Path filePath) {
this.filePath = filePath;
}
@Override
public void run() {
try {
Files.deleteIfExists(filePath);
} catch (IOException ignored) {
}
}
}
}
Note that if the Path object created by File.toPath would somehow retain a reference to the original TempFile2 instance, this wouldn’t work either, but luckily that is not how Path and (File#toPath()) is implemented.
As an aside, the general advice when it comes to Cleaner instances, is to create only one for your application or library, unless you think so many cleanup actions will get triggered by your code that a single thread is not sufficient to keep up.