Garbage Collection

Author: Tatyana Milkina
  1. Garbage Collector
  2. The finalize() Method
  3. Modern Alternatives to finalize()

1. Garbage Collector

Every time an object is created, memory is allocated for it. Memory is limited, and eventually it can run out. In some programming languages, developers must manually manage memory. In Java, memory management is automatic. The technology used for this task is called the garbage collector. You can request garbage collection manually using the System.gc() method. HOWEVER, the JVM decides whether to execute your request!

Garbage collection works as follows: when no references to an object exist, the program considers that the object is no longer needed, and the memory it occupies can be freed.

For example, in the main() method of the Cup class, a Cup object is created and referenced by the variable cup. After executing cup = null, the object still exists in memory but has no references. This object becomes a candidate for garbage collection. Additionally, the Cup object contains a reference to a Spoon object, which will also be collected.

public class Spoon {
}
public class Cup {
    Spoon spoon;

    Cup(Spoon spoon) {
        this.spoon = spoon;
    }

    public static void main(String[] args) {
        Cup cup = new Cup(new Spoon());
        cup = null;
     }
}

2. The finalize() Method

If an object interacts with resources, for example opens an output stream, the stream must be closed before the object is removed from memory. In Java, you can override the finalize() method, which is called by the Java runtime before deleting an object. Inside the finalize() method, you define the actions to execute before the object is destroyed. The finalize() method is called only just before garbage collection.

The finalize() method is not called when an object goes out of scope. It is unknown when (or if) the finalize() method will execute.

Most importantly, starting from Java 9 this method is not recommended, and in Java 18 it is deprecated for removal.

The finalize() method has several drawbacks and risks:

  • The call may be delayed indefinitely.
  • It can lead to subtle bugs and race conditions if synchronization or interaction with other objects is used.
  • It adds overhead for the garbage collector and may reduce performance.

However, here is an example of its usage.

We add finalize() to the Spoon and Cup classes and request garbage collection with System.gc() to test the finalize() call:

public class Spoon {
    @Override
    protected void finalize() {
        System.out.println("Spoon disappears forever");
    }
}
public class Cup {
    private Spoon spoon;

    public Cup(Spoon spoon) {
        this.spoon = spoon;
    }

    @Override
    protected void finalize()  {
        System.out.println("Cup disappears forever");
    }

    public static void main(String[] args) {
        Cup cup = new Cup(new Spoon());
        cup = null;
        System.gc();
     }
}

Execution result:

Spoon disappears forever
Cup disappears forever

3. Modern Alternatives to finalize()

The finalize() method is obsolete and unsuitable for modern resource management. Instead, it is recommended to use:

  • try-with-resources and the AutoCloseable interface for resource management.
  • java.lang.ref.Cleaner (after removal of finalize()).
Read also:
Comments