Java 9 with JShell
上QQ阅读APP看书,第一时间看更新

Understanding how garbage collection works

We will understand how garbage collection works with a few simple examples. We don't want to code a garbage collection process and we won't pe too deep into all the details about this complex process. Our goal is to create object-oriented code with Java 9 and not to create a new implementation of the JVM.

Now, we will add some code to the previously created Rectangle class to make Java execute some code before the garbage collection removes an instance of this class from memory. We only add this code to have a clear understanding of how garbage collection works. It is not recommended, and usually not necessary at all, to add code to the finalize method as we will do right now. Think about the next code as a code snippet just for educational purposes and not something we should do with our classes. We must take into account that we don't know when the garbage collection process will determine that it is necessary to claim back the memory used by the instances we will create. It is safe to do it but we cannot predict when the garbage collection process will do it. In this case, we are running JShell with the default garbage collection mechanism provided by the JVM.

Tip

You can follow best practices to release resources without having to add code to the finalize method. Remember that you don't know exactly when the finalize method is going to be executed. Even when the reference count reaches zero and all the variables that hold a reference have gone out of scope, the garbage collection algorithm implementation might keep the resources until the appropriate garbage collection destroys the instances. Thus, it is never a good idea to use the finalize method to release resources.

The following lines show the new complete code for the Rectangle class. The new lines are highlighted. The code file for the sample is included in the java_9_oop_chapter_03_01 folder, in the example03_10.java file.

class Rectangle {
    double width;
    double height;

    Rectangle(double width, double height) {
        System.out.printf("Initializing a new Rectangle instance\n");
        System.out.printf("Width: %.2f, Height: %.2f\n", 
            width, height);
        this.width = width;
        this.height = height;
    }

 // The following code doesn't represent a best practice
 // It is included just for educational purposes
 // and to make it easy to understand how the
 // garbage collection process works
 @Override
 protected void finalize() throws Throwable {
 try {
 System.out.printf("Finalizing Rectangle\n");
 System.out.printf("Width: %.2f, Height: %.2f\n", width, height);
 } catch(Throwable t){
 throw t;
 } finally{
 super.finalize();
 }
 }
}

The new lines declare a finalize method that overrides the inherited method from java.lang.Object and prints a message indicating that it is finalizing a Rectangle instance and displays the width and height values for the instance. Don't worry about the pieces of the code that you don't understand yet because we will learn them in the forthcoming chapters. The goal for the new piece of code included in the class is to let us know when the garbage collection process is going to remove the object from memory.

Tip

Avoid writing code that overrides the finalize method. Java 9 doesn't promote the usage of the finalize method to perform cleanup operations.

The following lines create two instances of the Rectangle class named rectangleToCollect1 and rectangleToCollect2. Then, the next lines assign null to both variables, and therefore, the reference count for both objects reaches zero and they become ready for garbage collection. The two instances can be safely removed from memory because there are no more variables in scope holding a reference to them. The code file for the sample is included in the java_9_oop_chapter_03_01 folder, in the example03_11.java file.

Rectangle rectangleToCollect1 = new Rectangle(51, 121);
Rectangle rectangleToCollect2 = new Rectangle(72, 282);
rectangleToCollect1 = null;
rectangleToCollect2 = null;

The following screenshot shows the results of executing the previous lines in JShell:

The two rectangle instances can be safely removed from memory but we don't see the messages indicating that the finalize method has been executed for each of these instances. Remember that we don't know when the garbage collection process will determine that it is necessary to claim back the memory used by these instances.

In order to understand how the garbage collection process works, we will force a garbage collection. However, it is very important to understand that we should never force a garbage collection in real-life applications. We must leave the JVM select the most appropriate time to perform a collection.

The next line shows the code that calls the System.gc method to force the JVM to perform a garbage collection. The code file for the sample is included in the java_9_oop_chapter_03_01 folder, in the example03_12.java file.

System.gc();

The following screenshot shows the results of executing the previous line in JShell. We will see the messages that indicate that the finalize method for the two instances has been called.

The following lines create an instance of the Rectangle class named rectangle5 and then assign a reference to this object to the referenceToRectangle5 variable. This way, the reference count to the object increases to two. The next line assigns null to rectangle5 and makes the reference count for the object to go down from two to one. The referenceToRectangle5 variable stills holds a reference to the Rectangle instance, and therefore, the next line that forces a garbage collection won't remove the instance from memory and we won't see the results of the execution of the code in the finalize method. There is still one variable on scope that holds a reference to the instance. The code file for the sample is included in the java_9_oop_chapter_03_01 folder, in the example03_13.java file.

Rectangle rectangle5 = new Rectangle(50, 550);
Rectangle referenceToRectangle5 = rectangle5;
rectangle5 = null;
System.gc();

The following screenshot shows the results of executing the previous lines in JShell:

Now, we will execute a line that assigns null to referenceToRectangle5 to force the reference count to reach zero for the referenced instance and we will force the garbage collection process to run in the next line. The code file for the sample is included in the java_9_oop_chapter_03_01 folder, in the example03_14.java file.

referenceToRectangle5 = null;
System.gc();

The following screenshot shows the results of executing the previous lines in JShell. We will see the messages that indicate that the finalize method for the instance has been called.

Tip

It is very important to know that you don't need to assign null to a reference to force the JVM to claim back the memory from objects. In the previous examples, we wanted to understand how the garbage collection worked. Java will automatically destroy the objects when they aren't referenced anymore in a transparent way.