Garbage Collector In Action

Karuna Rawat
Walmart Global Tech Blog
8 min readApr 22, 2021
Photo by Steve Johnson on Unsplash

Have you encountered OutOfMemoryError??

In my early days as a programmer, I used to often get OutOfMemoryError in a java applications. In order to fix the same, I used to increase the JVM heap size. After encountering it many times, I realized that from resource utilization point of view it’s not a good idea to increase the heap size without knowing the root cause. Let’s discuss here why do we encounter this problem.

Memory leaks ?? Objects which are eligible for Garbage Collection (GC) and do exist in our program are memory leaks.

Yes like C, C++ we do have memory leaks in java. Java Garbage Collector is a process in Java that takes care of memory leaks on the behalf of developers. Java developers also comes across “java.lang.OutOfMemoryError” exception. What is responsible for this exception ? It is memory leaks.
It points us to the fact that it is paramount to detect and take care of memory leaks. There are tools available for exactly this purpose. One such tool is JProfiler. JProfiler can be combined with different Integrated Development Environments (IDEs). For the rest of the blog, I will be using an IDE called IntelliJ.

Let’s take a simple example where a lot of heap is getting consumed by adding Employee objects to a map, assuming Java Garbage Collector will take care of memory leaks.

public class Sample {

public static void main(String[] args) throws InterruptedException {
Map<Employee, Integer> map = new HashMap<>();

while (true) {
for (int i = 0; i < 1000000; i++) {
map.put(new Employee("Kara"), 1);
}
Thread.sleep(100);
}
}
}

class Employee {
public String name;

public Employee(String name) {
this.name = name;
}
}

on running the application for some time, we will find ourselves in OutOfMemoryError exception as in one particular situation no space is left to allocate the Employee’s object in java heap. This is a memory leak as objects are getting created but not collected by GC. Increasing the heap size is a patch but not the solution. Therefore we should make the map available for GC by making it local to a method or some other way.

code

Now this is just one class application but it could be an application with 100 classes as well. To identify where exactly memory leak happened JProfiler can be used. It tells the classes which consumed the highest memory and could cause memory leak. Heap Walker is the section which tells us about the memory leaks. We can see Heap Walker(JProfiler snip) shows(red-marking) that it is HashMap and Employee class which is consuming a hell lot of memory.

JProfiler

Avoiding such situation is a programmer’s responsibility. In order to do so one must know how Java Garbage Collector works. One should also know how can an object become eligible for garbage collection.

This document mainly focuses on practical approaches of utilizing the GC while a developer writes code.

For further understanding please refer the links listed at the end of this article.

Java Garbage Collector

The Java Garbage Collector is a daemon thread which always runs in the background. It’s inside Java Virtual Machine and is responsible for destroying unused objects. Garbage Collector performs basically three tasks i.e.

  1. Mark objects for clean-up
  2. Do the actual clean up and
  3. Compact the memory space to enable contiguous memory allocation.

There are many GC algorithms like serial GC, parallel GC, concurrent mark, sweep(CMS), G1 and most of JVM follow mark and sweep algorithm. In order to enhance the performance, heap is further broken up into smaller parts or say generations such as Young, Old and Permanent generation. Minor and Major GC are performed on the heap by GC in time to time. All the minor and major garbage collections are “Stop the world” events not essentially but generally.

Ways to make an object available for Garbage Collection

  1. Nullifying the reference variable
Object redShirt = new Object();
redShirt = null; //now redShirt is eligible for garbage collector

2. Reassign the reference variable

Object redShirt = new Object();
Object blueShirt = new Object();
redShirt = blueShirt;

3. Objects created inside the method

Example 1

public class StudentOperation {
public static void main(String[] args) {
Student s = createStudent();
}
public static Student createStudent() {
Student s1 = new Student();
Student s2 = new Student();
return s1;
}
}
class Student {
}

s1 will be removed but before that it will pass ref to s in main method. So this object is not eligible for GC. Object pointed by s2 will be eligible for GC as soon as createStudent () method completes.s = createStudent() holds the object since we have a placeholder s for that. If Student s = createStudent() is replaced with createStudent() then both the s1 and s2 objects eligible for GC. So we should limit the scope of an object within a method if the object is not dealing at global level.

Example 2

public class StudentOperation {
static Student s1;
public static void main(String[] args) {
createStudent();
}
public static void createStudent() {
s1 = new Student();
Student s2 = new Student();
}
}
class Student {
}

since s1 is static variable so its lifetime is throughout the program, even after createStudent() called. So only s2 is eligible for GC here.

4. Island of isolation

class Student {
Student t;
public static void main() {
Student t1 = new Student();
Student t2 = new Student();
Student t3 = new Student();
t1.t = t2;
t2.t = t3;
t3.t = t1;
t1 = null;
t2 = null;
t3 = null;
}
}

till linet2 = null; no objects are eligible for GC. At line t3 = null; all three objects are eligible for GC because all objects are in isolation from any external references outside the heap.

 Island of isolation

Even though object has a reference variable, it may at times be eligible for GC (if all references are internal references)

Various ways for requesting JVM to run GC

Functioning: whenever we make an object eligible for garbage collector, does it mean the object gets destroyed immediately by JVM?

NO!! we don’t know when exactly JVM will run its Garbage Collector. We can request JVM to run its Garbage Collector and gc() method is a way to do so. Again, there is no guarantee whether our request is getting accepted, but most of the time if there are space crunches, JVM accepts our request.

  1. System class: it contains a static method gc()
System.gc();

2. Runtime class: Runtime class’s object enables java to communicate with JVM. Present in java.lang package, it is a singleton class and provides static factory method to create object and then call instance gc() method.

Runtime runtime = Runtime.getRuntime().gc();

If we talk about performance, Runtime.getRuntime().gc() is better than System.gc() because System.gc() internally calls Runtime’s class gc() method only.

public final class System {
public static void gc() {
Runtime.getRuntime().gc();
}
}

Example

class GCDemo {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
System.out.println("Total memory "+ runtime.totalMemory());
System.out.println("Free memory before running counter " + runtime.freeMemory());
for (int counter = 0; counter < 100000; counter++) {
Date date = new Date();
date = null;
}
System.out.println("Free memory after running counter " + runtime.freeMemory());
System.gc();
System.out.println("Free memory after running gc " + runtime.freeMemory());
}
}

Output

Total memory 257425408
Free memory before running counter 254741016
Free memory after running counter 251575920
Free memory after running gc 255567128

Finalization

  • Just before destroying an object, Garbage Collector calls Object class finalize() method to perform resource deallocation activities. finalize() on an object is called as part of gc() process only. This method can be overridden on need basis in one’s own classes. Does calling the finalize() method explicitly makes any difference? No, it will be treated as a regular user defined method call. Whenever JVM runs Garbage Collector, this method will get called automatically before gc() method call happens.
class GCFinalizeDemo {
public static void main(String[] args) {
GCFinalizeDemo gcFinalizeDemo = new GCFinalizeDemo();
gcFinalizeDemo.finalize();
gcFinalizeDemo.finalize();
gcFinalizeDemo = null;
System.gc();
System.out.println("GCFinalizeDemo work finished");
}

public void finalize() {
System.out.println("GCFinalizeDemo class's finalize method is called");
}
}

Output

GCFinalizeDemo class's finalize method is called
GCFinalizeDemo class's finalize method is called
GCFinalizeDemo work finished
GCFinalizeDemo class's finalize method is called
  • finalize() method belonging to a particular Class’s object(GC eligible) will be called here. For example below String class’s object is eligible for GC at line anotherObject = null; so String class’s finalize() method will be called not the GCFinalizeDemo’s finalize() method.
class GCFinalizeDemo {
public static void main(String[] args) {
String anotherObject = "eligibleforGC";
anotherObject = null;
System.gc();
System.out.println("GCFinalizeDemo work finished");
}

public void finalize() {
System.out.println("GCFinalizeDemo class's finalize method is called");
}
}

Output

GCFinalizeDemo work finished
  • JVM becomes partial when a developer calls finalize() method. A program will terminate abruptly when an uncaught exception happens during explicit finalize() call execution. When JVM internally makes such call, it ignores the exception and runs the program normally.
  • Garbage Collector calls finalize() only once on any object even though it is eligible for GC multiple times, as shown in example below.
public class FinalizeWorld {
static FinalizeWorld world1;

public static void main(String[] args) throws InterruptedException {
FinalizeWorld world2 = new FinalizeWorld();
System.out.println(world2.hashCode());
world2 = null;
System.gc();
Thread.sleep(5000);
System.out.println(world1.hashCode());
world1 = null;
System.gc();
Thread.sleep(5000);
System.out.println("End of main method");
}

public void finalize() {
System.out.println("Finalize method called");
}
}

Output

621009875
Finalize method called
621009875
End of main method

JVM behavior

Its random as we can’t predict how Garbage Collector identifies the objects for destruction, in which order Garbage Collector destroy the objects, whether it destroys all eligible objects or not and exact time when Garbage Collector runs. In below case if we do counter < 100000 may be JVM run GC, but how many object will get destroyed is unpredictable.

class GCDemo {
static int count = 0;

public static void main(String[] args) {
for (int counter = 0; counter < 10; counter++) {
GCDemo gcDemo = new GCDemo();
gcDemo = null;
}
}
public void finalize() {
count++;
System.out.println("finalize method called:" + count);
}
}

Here are some interesting links to find out more about GC

--

--