Originally published on Medium.
Java Garbage Collection
Imagine you have an office filled with documents, and every time you work on a document, you leave it scattered on your desk. Eventually, the desk becomes so cluttered that you can’t find the documents you need, and there’s no space left to work efficiently.
Introduction: Java garbage collection is an automatic process in the Java Virtual Machine (JVM) that frees up memory by removing objects no longer in use. This essential feature helps prevent memory leaks and reduces the need for manual memory management.

How Does the Garbage Collector Know What to Clean Up?
If you’re still working(referenced) on a document or it’s stored in your filing cabinet (variables and references in your program), the Garbage Collector leaves it alone.
Not in Use (Unreferenced) Documents: If you’ve left a document on your desk and forgotten about it (no variables or references pointing to it), the Garbage Collector picks it up and puts it away.
Types of Garbage Collectors in Java
- Serial Garbage Collector: The Serial GC is the simplest form of garbage collector in Java. It uses a single thread to perform all garbage collection work.
You go through each document one by one, deciding whether it’s still needed or not. Once you’ve sorted everything, you discard the unnecessary documents and neatly organize the ones you still need. This way, you create a tidy workspace and can work efficiently again.
How It Works: During garbage collection, the application is paused, and the single GC thread cleans up the memory. This is known as a “stop-the-world” event.
Use Case: Suitable for small applications with single-threaded environments where pause times are not critical.
You take a break from work to clean up the entire desk by yourself.
Here’s a Python example demonstrating this concept using a single-threaded garbage collection approach:
import gc
class MyClass:
def __init__(self, name):
self.name = name
print(f'Object {self.name} created')
def __del__(self):
print(f'Object {self.name} destroyed')
def create_objects():
obj1 = MyClass('obj1')
obj2 = MyClass('obj2')
# Create a cyclic reference
obj1.ref = obj2
obj2.ref = obj1
return obj1, obj2
def perform_serial_gc():
# Disable automatic garbage collection
gc.disable()
# Manually invoke garbage collection
print('Starting garbage collection...')
gc.collect()
print('Garbage collection complete')
# Create objects and form cyclic references
obj1, obj2 = create_objects()
# Manually perform garbage collection
perform_serial_gc()
# Remove references to objects
del obj1
del obj2
# Perform garbage collection again to clean up
perform_serial_gc()
2. Parallel Garbage Collector: The Parallel GC, also known as the “Throughput Collector,” uses multiple threads to speed up the garbage collection process.
You and several of your colleagues decide to clean up the desk together. Each of you takes a portion of the documents, sorting through them simultaneously. Some colleagues focus on sorting, while others handle discarding unnecessary documents and organizing the ones still needed. By working in parallel, you manage to clean up the desk much faster, creating a tidy workspace where everyone can work efficiently again.
How It Works: It uses multiple threads to collect garbage in the Young Generation, making it efficient for applications running on multi-core processors.
Use Case: Ideal for applications that can tolerate longer pause times but require high throughput, such as batch processing systems.
You and several colleagues stop working and clean up the desk together, each handling different sections simultaneously.
Here’s a Python example demonstrating this concept using a parallel garbage collection approach:
import gc
import threading
class MyClass:
def __init__(self, name):
self.name = name
print(f'Object {self.name} created')
def __del__(self):
print(f'Object {self.name} destroyed')
def create_objects():
obj1 = MyClass('obj1')
obj2 = MyClass('obj2')
# Create a cyclic reference
obj1.ref = obj2
obj2.ref = obj1
return obj1, obj2
def perform_gc():
print('Starting garbage collection...')
gc.collect()
print('Garbage collection complete')
def parallel_gc(num_threads):
threads = []
for _ in range(num_threads):
thread = threading.Thread(target=perform_gc)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
# Create objects and form cyclic references
obj1, obj2 = create_objects()
# Perform parallel garbage collection using multiple threads
parallel_gc(num_threads=4)
# Remove references to objects
del obj1
del obj2
# Perform parallel garbage collection again to clean up
parallel_gc(num_threads=4)
3. Concurrent Mark-Sweep (CMS) Collector: The CMS collector aims to minimize pause times by performing most of its work concurrently with the application.
You and your colleagues decide to clean up the desk while continuing to work. One group of colleagues starts by marking all the documents you still need as they notice them, while another group continues working as usual. During a brief break, the first group sweeps through the desk, quickly discarding the documents that were not marked as needed. By marking documents in the background and only briefly pausing to discard the unnecessary ones, you manage to keep the workspace tidy without significantly interrupting your work. This way, the desk stays organized, and you can work efficiently with minimal disruption.
How It Works: It has four phases: initial mark, concurrent mark, remark, and concurrent sweep. Only the initial mark and remark phases cause pauses.
Use Case: Suitable for applications requiring low latency and can afford some CPU overhead for concurrent GC activities.
One group of colleagues marks needed documents while others continue working. During a brief break, the marked documents are sorted and the unnecessary ones are discarded.
Here’s a Python example demonstrating this concept using a Concurrent Mark-Sweep collection approach:
import gc
import threading
import time
class MyClass:
def __init__(self, name):
self.name = name
print(f'Object {self.name} created')
def __del__(self):
print(f'Object {self.name} destroyed')
def create_objects(num_objects):
objects = []
for i in range(num_objects):
obj = MyClass(f'obj{i}')
objects.append(obj)
return objects
def mark_phase():
print('Starting mark phase...')
gc.collect()
print('Mark phase complete')
def sweep_phase():
print('Starting sweep phase...')
gc.collect()
print('Sweep phase complete')
def concurrent_mark_sweep_gc():
# Concurrent Mark Phase
mark_thread = threading.Thread(target=mark_phase)
mark_thread.start()
mark_thread.join()
# Concurrent Sweep Phase
sweep_thread = threading.Thread(target=sweep_phase)
sweep_thread.start()
sweep_thread.join()
# Create objects
objects = create_objects(num_objects=10)
time.sleep(0.5) # Simulate time delay for object usage
# Perform CMS garbage collection
concurrent_mark_sweep_gc()
# Remove references to objects
for obj in objects:
del obj
# Perform CMS garbage collection again to clean up
concurrent_mark_sweep_gc()
4. Garbage-First (G1) Collector: The G1 collector is designed for applications with large heaps and aims to provide predictable pause times.
You and your colleagues decide to tackle the most cluttered areas of the desk first, where the mess is the worst. Each of you targets these high-priority sections, quickly sorting through the documents to identify which ones are still needed and which ones can be discarded. Once the most cluttered areas are clean, you gradually move on to the less messy sections. By focusing on the worst messes first, you efficiently clean up the entire desk, ensuring that the workspace remains organized and easy to work in.
How It Works: G1 divides the heap into regions and collects garbage in regions that contain the most reclaimable space first. It uses a mix of concurrent and parallel phases.
Use Case: Ideal for large-scale applications needing predictable pause times and efficient memory management.
You and your colleagues focus on the messiest parts of the desk first, working together to clean those up, and then gradually move on to less messy sections. The work stops briefly during the cleanup but is optimized to minimize disruption.
Here’s a Python example demonstrating this concept using a Garbage-First collection approach:
import gc
import threading
import random
import time
class MyClass:
def __init__(self, name):
self.name = name
print(f'Object {self.name} created')
def __del__(self):
print(f'Object {self.name} destroyed')
def create_objects(num_objects):
objects = []
for i in range(num_objects):
obj = MyClass(f'obj{i}')
objects.append(obj)
# Randomly create cyclic references
for obj in objects:
obj.ref = random.choice(objects)
return objects
def perform_gc(region):
print(f'Starting garbage collection in region {region}...')
gc.collect()
print(f'Garbage collection complete in region {region}')
def g1_gc(num_regions):
threads = []
for region in range(num_regions):
thread = threading.Thread(target=perform_gc, args=(region,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
# Create objects in different regions
num_regions = 4
all_objects = []
for _ in range(num_regions):
all_objects.extend(create_objects(num_objects=10))
time.sleep(0.5) # Simulate time delay between object creation
# Perform G1 garbage collection
g1_gc(num_regions=num_regions)
# Remove references to objects
for obj in all_objects:
del obj
# Perform G1 garbage collection again to clean up
g1_gc(num_regions=num_regions)