Core Java

Java destructor – Why is it missing?

In this article we will explain what a destructor is, why Java is missing one and what alternatives are provided by the language itself.

Java is often compared to C++ in terms of being a high level*, object-oriented language. One of their major differences though, is that Java lacks a destructor element, and instead uses a garbage collector for resource deallocation. It is possible to use something similar to a destructor in Java, the method Object.finalize(), which however does not work exactly like a standard destructor  would.

*There are arguments on whether or not C++ should be considered a high or low level language. In this context we will use the definition that any machine independent language is high level, so C++ is included. It doesn’t make a difference for the rest of the article though, just a side note to make the comparison more clear.

1. Destructor and Garbage Collector

Let’s take a look on some definitions, to understand exactly what the destructor and the garbage collector do.

  • Destructor: It is a special method called when the object’s lifecycle is over, in order to free memory and deallocate resources. It is very prominent in languages with manual memory management (where the developer has to invoke in explicitly), and extremely important to use it in order to avoid memory leaks.
  • Garbage Collector: The garbage collector is a program that runs on the JVM and recovers memory by deleting objects that are not used anymore or are not accessible from the code (and are considered garbage, hence the name). It runs automatically and periodically checks the references in contrast to the objects in the memory heap. If an unreferenced object is found, that means that there is no way to access it anymore and it is useless, so the garbage collector gets rid of it and frees the memory.

As you can clearly see the main difference between the two of them is the level of the developer’s interference to memory management. By having to use a destructor, the developer states exactly when the object will be destroyed, whereas in a language that uses the garbage collection method (like Java), the existing garbage collector does everything by itself. These two different ways of approach have both positives and negatives, but the main issue here is that sometimes the developer needs more immediate access to memory management.

Java provides some ways of accessing and instructing the garbage collector, but unfortunately they are not definite, as the non-deterministic nature of the GC makes it impossible to force its execution. The best bet for the developer is to try to force the GC to run by using the method System.gc() or Runtime.getRuntime().gc(), which unfortunately is the equivalent of saying to the GC “I would really like to start freeing memory now, but as always, it’s up to you”. So, what are the alternatives?

2. The Object.finalize() method

The finalize() method is inherited in all Java objects (since the Java convention is that everything is a sub-class of Object). This method is NOT a destructor! Instead, it is supposed to be used in order to provide additional safety in cases when you need to be sure that the use of external resources (like opening and closing a file, or a socket, or a stream) will be done correctly, which means closing everything before the program shutdown. You can invoke the finalizers to run either by using the method itself (Object.finalize()) or by using the method System.runFinalizersOnExit(true).

At this point we should make clear that using finalizers at all is highly not recommended. They are considered very unsafe and in many cases used completely incorrectly, like when they are used to delete the object itself. They also add overhead to the GC, making your program slower! Due to the GC, it is not known when the finalizer will run, and this can potentially create problems with supposedly removed objects that still exist, which can of course be the source of bugs and headaches. This is made very clear from the Java documentation of System.runFinalizersOnExit(true), where it is stated that “This method is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, resulting in erratic behavior or deadlock.

3. Example of finalize()

Let’s take a look on a safer way to use finalize(). In this example we are opening a few streams, and use them, and in the end we call System.runFinalizersOnExit(true) in order to close all the remaining open streams by having implemented a finalize() method in this class.

FinalizeExampleMain.java

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.OutputStream;


public class FinalizeExampleMain {
    
    // We are declaring the streams that we are going to use here.
    private OutputStream out;
    private BufferedReader reader;
    private FileReader fileReader;
    
    public FinalizeExampleMain() {
        try {
            // On instantiation of the class, we also instantiate the streams
            // that we need to use in the example. We will have to close them
            // afterwards, and this is where finalize() will be used.
            out = new FileOutputStream("example.txt");
            fileReader = new FileReader("example.txt");
            reader = new BufferedReader(fileReader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // Just some public accessors for the needed streams.
    public OutputStream getOutputStream() {
        return out;
    }
    
    public BufferedReader getReader() {
        return reader;
    }
    
    // The finalize() method. As seen, we are using it to close all the
    // open streams and NOT to destroy the object itself, as this is useless
    // and dangerous. The GC will take care of that.
    @Override
    public void finalize() throws Throwable {
        try {
            System.out.println(this.getClass().getName() + " is finalized and streams closing!");
            if (out != null) {
                out.close();
                System.out.println("OutputStream closed!");
            }
            if (fileReader != null) {
                fileReader.close();
                System.out.println("FileReader closed!");
            }
            if (reader != null) {
                reader.close();
                System.out.println("BufferedReader closed!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("All done!");
            super.finalize();
        }
    }

    // This annotation here is provided by eclipse, because the method
    // System.runFinalizersOnExit(true) that we are using is deprecated and 
    // generally should not be used. We are using it here to show how finalization
    // works, but it is not recommended.
    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        System.out.println("Starting program...");
        FinalizeExampleMain main = new FinalizeExampleMain();
        
        try {
            // Get the output stream and write a String in the file (in form of bytes).
            main.getOutputStream().write("This is an example.".getBytes());
            
            // Just to make sure that the streams are open and working correctly,
            // We use a BufferedReader to read the file and print everything in it
            // on the stdout.
            String line;
            while ((line = main.getReader().readLine()) != null) {
                System.out.println("Line read: " + line + "\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        // Here we are forcing the program to run all the implemented
        // finalize() methods when the program finishes. Again, not recommended!
        System.runFinalizersOnExit(true);
        System.out.println("Program exiting. The finalizers should run now.");
    }
}

Output:

Starting program...
Line read: This is an example.

Program exiting. The finalizers should run now.
FinalizeExampleMain is finalized and streams closing!
OutputStream closed!
FileReader closed!
BufferedReader closed!
All done!

As seen, when the program is about to end, the finalizers start running and all the open streams are closed. You should not confuse the streams with the files themselves. The file example.txt that we have created still exists in the program directory, but the streams connecting it to our application are not live.

4. Download the code

This was an article about the finalize() method and how it compares with traditional destructors.
You can download the Eclipse project here: FinalizeExample

Ilias Koutsakis

Ilias has graduated from the Department of Informatics and Telecommunications of the National and Kapodistrian University of Athens. He is interested in all aspects of software engineering, particularly data mining, and loves the challenge of working with new technologies. He is pursuing the dream of clean and readable code on a daily basis.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Priya
6 years ago

Java does not support destructors because they are used to free the memory when the object losses its scope. In java, garbage collector is used to free the memory allocated for the object when it losses its scope.

George
George
5 years ago

Is there an better option for Java?
How would it be to use a Cleanup-Class always running at the end of the programm with a function addCleanAction(Callable cleanIt)?
cleanIt is a lamda function. You may need to check any Objects of being null.
Related to the observer paddern: https://en.wikipedia.org/wiki/Observer_pattern

Back to top button