The Best Fluffy Pancakes recipe you will fall in love with. Full of tips and tricks to help you make the best pancakes.
Memory management is a critical aspect of modern software development. And in Java, developers enjoy the luxury of automatic memory management thanks to the JVM’s Garbage Collector. Unlike languages where developers must manually allocate and free memory, java automates this task thus reducing the chances of memory leaks and dangling pointers. This built-in garbage collector allows developers to focus more on writing code rather than worrying about managing memory. But have you ever wondered what really happens under the hood? And more importantly what happens when garbage collector is disabled?
In this post, I will cover how garbage collection works and show a demo on what happens if its disabled using EpislonGC
– a no-op garbage collector.
What is Garbage Collection in Java?
In Java, there are mainly three regions in the memory where variables are stored at runtime:
- Heap – It stores objects and their instance variables
- Stack – It stores local variables and method execution data
- Method Area – Also sometimes referred as static stores class-level data, including static variables
public class Tester {
public static void main(String[] args) {
int age = 30; // Primitive on stack
String greeting = "Hello, World!"; // String literal in method area
Person perVar = new Person("Alice", age); // Object on heap
perVar.sayHello(greeting);
}
}
class Person {
private String name; // Reference to String in method area
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello(String msg) {
System.out.println(name + " says: " + msg);
}
}

In Java, at runtime all objects are stored in the heap memory. When an object is no longer referenced by any part of the code, it becomes eligible for garbage collection. For example, in the above code snippet, after the method call perVar.sayHello(greeting);
, the person reference is set to null perVar = null;
it will become eligible for garbage collection as there are no other references for perVar
.
The garbage collector in Java periodically scans the heap memory to find the objects that are no longer used. The process of garbage collector involves several steps, including marking, sweeping and compacting.
Marking – The first step in garbage collection is marking all the objects that are still being referenced in the code.
Sweeping – After the marking step, the garbage collector sweeps through Java heap to identify and reclaim the memory that is being used by the objects are no longer being referenced in the code. It deallocates the memory and add it back to the free memory pool.
Compacting – In some garbage collection algorithms, after the sweeping step compaction step is ran. In this step, all the objects that are still being referenced are moved closer and thus creating a large contiguous block of free memory.

Without a garbage collector, every object allocated will stay in memory forever unless you manually nullify and clean it up.
What happens without GC?
Short answer your memory will fill up and you will get OutOfMemoryError
.
Long answer, let’s say you have a bug in your program and it keeps on creating new objects which are always being referenced, after a certain point of time there will no space left in JVM heap to allocate memory for new objects. And you will receive an error in your program OutOfMemoryError
.
When an OutOfMemoryError
occurs, the application usually crashes and terminates. Let’s see it with an example.
JVM does not allow you to completely disable garbage collection as its an integral part of Java’s memory management system. But what it does allow is to use Epsilon GC – a no-op garbage collector that does not reclaim memory even if the objects are eligible for garbage collection.
To enable the Epsilon GC we need to pass -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
in VM arguments.
public class MemoryLeakDemo {
public static void main(String[] args) throws IOException {
for (int i = 0; i < 10000; i++) {
// Allocate 100MB
byte[] block = new byte[100 * 1024 * 1024];
System.out.println("Allocated " + ((i + 1) * 100) + "MB");
// Pause to inspect in VisualVM or wait for OOM
if (i != 0 && i % 10 == 0)
System.in.read();
}
// Pause to inspect in VisualVM or wait for OOM
try {
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
}
In the above code, inside each iteration 100MB array block is getting created and its reference is never released (from heap as Epsilon GC does not do garbage collection).
As the loop continues, the memory allocation will keep increasing and run into the problem where no memory is left and will throw OutOfMemoryError
and application will crash. On my machine the heap size grew to 3GB and crashed after no memory was left on the machine.

Now, lets see how the same code behaves when we are not using Epsilon GC (in here I am using the default G1 GC in JDK 21).
public class MemoryFreeDemo {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
// Allocate 100MB
byte[] block = new byte[100 * 1024 * 1024];
System.out.println("Allocated " + ((i + 1) * 100) + "MB");
}
// Pause to inspect in VisualVM
try {
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
}
In the above code, inside each iteration 100MB array block is getting created but as we are using G1 GC the memory is continuously freed.
As the loop continues, the memory will be continuously allocated and deallocated by the GC and the program will never run into OutOfMemoryError
. In my machine, the peak memory allocation in the heap was around 700MB.

Java garbage collection benefits
Here are some of the benefits of Java garbage collection:
- No need to manual memory management – With Java’s automatic garbage collection, developers do not have to manually allocate and deallocate memory.
- Prevents memory leak – Garbage collection helps prevent memory leaks, which can occur when the program is not releasing memory which eventually leads to application crashing.
- Dynamic memory allocation – Java’s garbage collection allocates memory dynamically as needed at runtime.
- Improved performance – By automatic garbage collection, java reduces the overhead of memory management.
- Memory optimization – Garbage collection optimizes the use of memory by reusing the unused memory.
Summary
- Garbage collection is not just convenience – it’s a safety net that allows modern java application to run efficiently without memory leaks.
- Epsilon GC helped to visualize the cost of no garbage collection –
OutOfMemoryError
s, program crashing. - Garbage collection performs its job under the hood – but as a developer we should write memory-efficient code.
Source Code
The code used in above example can be found at Github.