Garbage Collection in Java
In this article, we will cover garbage collection in Java. We’ll look at the Garbage Collector and discuss the conditions that make an object eligible for collection. We’ll also explore the topics of object reachability and object cleanup. Finally, we’ll show how to call the garbage collector in our code.
You can also check this tutorial in the following video:
1. Introduction
When writing an application, you may need to allocate memory to store data. In some languages, this is done explicitly. In C, for example, you use the malloc
function to allocate memory for data structures. In other languages, this is done implicitly. In Java, you use the new
operator to create an object. The JVM will then allocate memory to store the object.
But how do we free up memory when it is no longer being used? In C you can de-allocate memory with the free
function. Java uses a garbage collector for this task.
2. Garbage Collection in Java
Memory management is an important concern in any application or program. It can help prevent such issues as memory leaks that can cause your application to run out of memory.
Most modern languages, such as Java, use dynamic memory allocation and garbage collection to manage data stored in memory. This is a boon to programmers since allocating and releasing memory can be tedious and error-prone.
Garbage collection in Java is the process of reclaiming memory occupied by objects that are no longer used by the running application. The JVM runs a low priority background thread, called the Garbage Collector, to free up this unused memory.
2.1 The Garbage Collector (GC)
So how, you may ask, does the GC accomplish this task? Garbage collection is generally a two-step process.
- The GC identifies the objects that can be reached by a live thread (through a reference) and marks them as live objects. If there are no references to an object, it is considered unreachable and eligible for garbage collection.
- Then, at some unspecified juncture, the GC will remove unreferenced objects and reclaim its memory. (Technically, the memory is freed and available to store new objects.)
2.2 Objects Eligible for Garbage Collection
Let’s see a simple example that shows how an object can become eligible for garbage collection.
public class LocalObject { public static void main(String[] args) { someMethod(); System.exit(0); } static void someMethod() { MyObject myObj = new MyObject(); System.out.println(myObj.sayHello()); } } class MyObject { public String sayHello() { return "Hello!"; } }
To understand this example, we first need to talk about heap space and stack memory. The heap space is where objects are stored. The stack memory is where method execution occurs. It holds primitive values and references (pointers) to objects in the heap.
During the execution of someMethod
, an object of type MyObject
is created in the heap and a reference to it (myObj
, a local variable) is stored in the stack. When someMethod
terminates, the reference to MyObject
(myObj
) goes out of scope, since it is a local variable. MyObject
is now unreachable, making it eligible for garbage collection.
2.3 Reachable Objects
The example above serve to acquire a rudimentary understanding of how garbage collection works but most scenarios are not so clear cut. Applications can create scores of objects that are interrelated. What objects are unreachable in these situations? We can use an object reachability graph to determine this.
To better understand this graph, we need to talk about garbage collection roots. A GC root is an object that is accessible from outside the heap space. Examples of GC roots are:
- System Classloaders
- System Classes
- Threads
- Static Variables
The GC traverses the objects in the graph beginning from the GC roots. Any objects that cannot be reached are dead objects and eligible for garbage collection.
Static variables are GC roots. Let’s look at an example.
public class StaticObject { static MyObject myObj = new MyObject(); public static void main(String[] args) { someMethod(); System.out.println("In main - " + myObj.sayHello()); } static void someMethod() { System.out.println("In someMethod - " + myObj.sayHello()); } } class MyObject{ public String sayHello() { return "Hello!"; } }
Static members (variables and methods) are associated with the class itself and not instances of the class. This means you can access them from anywhere in the application. Static variables are initialized when the class is loaded and before any instances of the class are created. Since they are loaded into the heap space by the classloader with the class, they cannot be garbage collected and remain in memory for the duration of the program. (Note: You should use static variables judiciously for this reason.)
In the example above, an object of type MyObject
is created and tied to the static variable myObj
when the StaticObject
class is loaded into memory by the classloader. MyObject
remains in the heap space, even when the program ends since it referenced by a static variable.
2.4 Causing Objects to Become Eligible for GC
As shown in the first example, an object created within a method will become eligible for garbage collection when the method exits, since there is no longer any reference to it. But you can explicitly make an object eligible within a method or constructor. Let’s look at two examples.
public class NullifyObject { public static void main(String[] args) { MyObject myObj = new MyObject(); myObj.sayHello(); myObj = null; myObj.sayHello(); } } class MyObject{ public String sayHello() { return "Hello!"; } }
When variable myObj
is assigned to null, the previously assigned object is no longer reachable and is eligible for CG.
Another way an object can become eligible is through re-initialization.
public class ReinitObject { public static void main(String[] args) { MyObject myObj_A = new MyObject(); MyObject myObj_B = new MyObject(); System.out.println(myObj_A.toString()); System.out.println(myObj_B.toString()); myObj_A = myObj_B; System.out.println(myObj_A.toString()); System.out.println(myObj_B.toString()); } } class MyObject { OtherObject otherObj = new OtherObject(); } class OtherObject { }
When we reinitialize the variable myObj_A
to the object pointed to by myObj_B
, the original object is no longer reachable and therefore eligible for GC. Also, note that any instance variable referenced objects of the original object (e.g OtherObject
) are also no longer reachable and eligible for collection.
2.5 Cleaning Up
If you are writing a class that uses resources, you will want to release those resources before the object is garbage collected. The finalize
method from the java.lang.Object
class was sometimes used for this purpose but there are many problems associated with its use and it has been deprecated.
One option for cleaning up your object before it is destroyed is to have your class implement the AutoCloseable
interface and use it in conjunction with a try-with-resources
block.
ShowFile.java
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; public class ShowFile implements AutoCloseable { BufferedReader inputStream; public String returnContentsOfFile() throws IOException { inputStream = new BufferedReader( new FileReader(getFile("quote.txt"))); StringBuffer output = new StringBuffer(); String line; while ((line = inputStream.readLine()) != null) { output.append(line + "\n"); } return output.toString(); } private File getFile(String fileName) { return new File( this.getClass() .getClassLoader() .getResource(fileName) .getFile()); } @Override public void close() throws Exception { System.out.println("Cleaning up resources..."); inputStream.close(); } }
The AutoCloseable
interface has one method, close
. This method is automatically called, regardless of whether the try statement ends normally or in an exception.
ShowFileDriver.java
public class ShowFileDriver { public static void main(String[] args) { try(ShowFile sFile = new ShowFile()){ System.out.println(sFile.returnContentsOfFile()); } catch (Exception e) { // Handle the exception } } }
Running this program will demonstrate that the close
method was called when the try
statement ended.
I think therefore I am Be yourself; everyone else is already taken So many books, so little time Cleaning up resources...
2.6 Explicitly Calling the Garbage Collector
As previously explained, the garbage collector runs as a low priority background thread. However, you can request the JVM to run the collector using the System or Runtime classes:
System.gc()
Runtime.getRuntime().gc()
It is not regarded as good practice to call the GC directly because:
- It does not guarantee the GC will run right away or that it will even run at all.
- Running the GC takes up resources. This may adversely affect the performance of your application. For example, it may suspend all user threads while the process runs.
You may consider calling the GC explicitly if you are debugging memory issues in an application.
3. Summary
In this article, we examined garbage collection in Java. We looked at the Garbage Collector and discussed what circumstances make an object eligible for collection. We also explored the topics of object reachability and object cleanup. Finally, we demonstrated how to call the garbage collector in our code.