Java Cleaner is a new feature introduced in Java 9 that allows you to define cleanup actions for groups of objects. Cleaners are implemented with the Cleanable interface, which descends from Runnable. Each Cleanable represents an object and a cleaning action registered in a Cleaner. Each Cleanable runs in a dedicated thread. All exceptions thrown by the cleaning action are ignored.

Java 18, marked the finalize method for removal. It is better to avoid the usage of finalize method.

The most efficient use of Cleaner is to explicitly invoke the clean() method when the object is closed or no longer needed. Note that the cleaning action must not refer to the object being registered.

Cleaners provide a more reliable way to perform cleanup tasks than relying on finalization. Finalization is often unreliable because it is not guaranteed to be called before the object is garbage collected. Cleaners, on the other hand, are guaranteed to be called before the object is garbage collected, as long as the clean() method is invoked.

See the sample usage of a Java Cleaner below:

import java.lang.ref.Cleaner;

public class SampleResourceUsage implements AutoCloseable {

    /**
     * A cleaner, preferably one shared within a library
     */
    private static final Cleaner cleaner = Cleaner.create();

    /**
     * An instance of Cleaner.Cleanable that the register returned.
     */
    final private Cleaner.Cleanable cleanable;

    /**
     * The resource that should be cleaned after use.
     */
    final private CloseableResource resource;

    public SampleResourceUsage() {
        //Instantiate a resource that must be cleaned after use.
        resource = new CloseableResource();

        //Register with the cleaner.
        cleanable = cleaner.register(this, resource::close);
    }

    public void use() {
        resource.use();
    }

    @Override
    public void close() {
        //Use clean method from the Cleanable instance to close the resource.
        cleanable.clean();
    }

    public static void main(String ... args) throws InterruptedException {
        //Create an instance with cleanable resource.
        var resource = new SampleResourceUsage();
        resource.use();

        //Remove the instance reference.
        resource = null;

        //Request for a garbage collection.
        System.gc();

        //Try to wait for a garbage collection to complete.
        //If garbage collection was not triggered. Increase the timeout.
        Thread.sleep(1000);
    }

    /**
     * The class that should have clean up operation.
     */
    private static class CloseableResource {
        public void use() {
            System.out.println("Using the resource that must be cleaned.");
        }

        /**
         * The cleaner that must be called for garbage collection.
         */
        public void close() {
            System.out.println("Cleaning up.");
        }
    }
}

Expect to see the following output:

Using the resource.
Cleaning up.

If you didn't see preceding output, you might need to check the timeout value.

Here are some additional things to keep in mind when using Java Cleaner:

  • The Cleaner object is a heavyweight object, so it should be shared whenever possible.
  • The Cleaner thread is a daemon thread, so it will not prevent your program from exiting.
  • The clean() method can only be called once. If you need to perform multiple cleanup actions, you should use a Runnable object to wrap the cleanup actions.