Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this short tutorial, we’ll look at the possibility of destroying objects in Java.

2. Destructor in Java

Every time we create an object, Java automatically allocates the memory on the heap. Similarly, whenever an object is no longer needed, the memory will automatically be deallocated.

In languages like C, when we finish using an object in memory, we have to deallocate it manually. Unfortunately, Java doesn’t support manual memory deallocation. Moreover, one of the features of the Java programming language is taking care of object destruction by itself – using a technique called garbage collection.

3. Garbage Collection

Garbage collection removes unused objects from memory on the heap. It helps prevent memory leaks. Simply put, when there are no more references to the specific object and the object is no longer accessible, the garbage collector marks this object as unreachable and reclaims its space.

Failure to handle garbage collection properly can cause performance problems, and eventually, it causes an application to run out of memory.

An object can be garbage collected when it reaches a state of no longer being accessible in the program. An object is no longer reachable when one of two situations occurs:

  • The object doesn’t have any references pointing to it
  • All references to the object have gone out of scope

Java includes the System.gc() method to help support garbage collection. By calling this method, we can suggest to the JVM to run the garbage collector. However, we cannot guarantee the JVM will actually call it. The JVM is free to ignore the request.

4. Finalizer

The Object class provides the finalize() method. Before the garbage collector removes an object from memory, it’ll call the finalize() method. The method can run zero or one time. However, it cannot run twice for the same object.

The finalize() method defined inside the Object class doesn’t perform any special action.

The main goal of the finalizer is to release resources used by the object before its removal from the memory. For instance, we can override the method to close the database connections or other resources.

Let’s create a class that contains the BufferedReader instance variable:

class Resource {

    final BufferedReader reader;

    public Resource(String filename) throws FileNotFoundException {
        reader = new BufferedReader(new FileReader(filename));
    }

    public long getLineNumber() {
        return reader.lines().count();
    }
}
In our example, we didn’t close our resources. We can close them inside the finalize() method:
@Override
protected void finalize() {
    try {
        reader.close();
    } catch (IOException e) {
        // ...
    }
}

When JVM calls the finalize() method, the BufferedReader resource will be released. The exceptions thrown by the finalize() method will stop the object finalization.

However, since Java 9, the finalize() method has become deprecated. Using finalize() method can be confusing and hard to use properly.

If we want to release resources held by an object, we should consider implementing the AutoCloseable interface instead. Classes like Cleaner and PhantomReference provide a more flexible way to manage resources once an object becomes unreachable.

4.1. Implementing AutoCloseable

The AutoCloseable interface provides the close() method, which will be executed automatically when exiting a try-with-resources block. Inside this method, we can close resources used by an object.

Let’s modify our example class to implement the AutoCloseable interface:

class Resource implements AutoCloseable {

    final BufferedReader reader;

    public Resource(String filename) throws FileNotFoundException {
        reader = new BufferedReader(new FileReader(filename));
    }

    public long getLineNumber() {
        return reader.lines().count();
    }

    @Override
    public void close() throws Exception {
        reader.close();
    }
}

We can use the close() method to close our resources instead of using the finalize() method.

4.2. Cleaner Class

We can use the Cleaner class if we want to perform specific actions when an object becomes phantom reachable. In other words, when an object becomes finalized and its memory is ready to be deallocated.

Now, let’s see how to use the Cleaner class. Firstly, let’s define Cleaner:

Cleaner cleaner = Cleaner.create();

Next, we’ll create a class that contains a cleaner reference:

class Order implements AutoCloseable {

    private final Cleaner cleaner;

    public Order(Cleaner cleaner) {
        this.cleaner = cleaner;
    }
}

Secondly, we’ll define a static inner class that implements Runnable inside the Order class:

static class CleaningAction implements Runnable {

    private final int id;

    public CleaningAction(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        System.out.printf("Object with id %s is garbage collected. %n", id);
    }
}

Instances of our inner class will represent cleaning actions. We should register each cleaning action in order for them to run after an object becomes phantom reachable.

We should consider not using a lambda for the cleaning action. By using a lambda, we could easily capture the object reference, preventing an object from becoming phantom reachable. Using a static nested class, as above, will avoid keeping the object reference.

Let’s add the Cleanable instance variable inside the Order class:

private Cleaner.Cleanable cleanable;

The Cleanable instance represents the cleaning object that contains the cleaning action.

Next, let’s create a method that will register the cleaning action:

public void register(Product product, int id) {
    this.cleanable = cleaner.register(product, new CleaningAction(id));
}

Finally, let’s implement the close() method:

public void close() {
    cleanable.clean();
}

The clean() method unregisters the cleanable and invokes registered cleaning actions. This method will be called at most once regardless of the number of calls to clean.

When we use our CleaningExample instance inside a try-with-resources block, the close() method calls the cleaning action:

final Cleaner cleaner = Cleaner.create();
try (Order order = new Order(cleaner)) {
    for (int i = 0; i < 10; i++) {
        order.register(new Product(i), i);
    }
} catch (Exception e) {
    System.err.println("Error: " + e);
}

In other cases, the cleaner will call the clean() method when an instance becomes phantom reachable.

Additionally, the behavior of cleaners during the System.exit() is implementation-specific. Java provides no guarantees whether cleaning actions will be invoked or not.

5. Conclusion

In this short tutorial, we looked at the possibility of object destruction in Java. To sum up, Java doesn’t support manual object destruction. However, we can use finalize() or Cleaner to free up the resources held by an object. As always, the source code for the examples is available over on GitHub.
Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are closed on this article!