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.