A Java object may declare a finalize method. This method is called just before Java releases the memory for the object. It will typically look like this:

public class MyClass {
  
    //Methods for the class

    @Override
    protected void finalize() throws Throwable {
        // Cleanup code
    }
}

However, there some important caveats on the behavior of Java finalization.

The caveats above mean that it is a bad idea to rely on the finalize method to perform cleanup (or other) actions that must be performed in a timely fashion. Over reliance on finalization can lead to storage leaks, memory leaks and other problems.

In short, there are very few situation where finalization is actually a good solution.

Finalizers only run once

Normally, an object is deleted after it has been finalized. However, this doesn’t happen all of the time. Consider the following example1:

public class CaptainJack {
    public static CaptainJack notDeadYet = null;

    protected void finalize() {
        // Resurrection!
        notDeadYet = this;
    }
}

When an instance of CaptainJack becomes unreachable and the garbage collector attempts to reclaim it, the finalize() method will assign a reference to the instance to the notDeadYet variable. That will make the instance reachable once more, and the garbage collector won’t delete it.

Question: Is Captain Jack immortal?

Answer: No.

The catch is the JVM will only run a finalizer on an object once in its lifetime. If you assign null to notDeadYet causing a resurected instance to be unreachable once more, the garbage collector won’t call finalize() on the object.

1 - See https://en.wikipedia.org/wiki/Jack_Harkness.