How Java Works — Part 5
How Garbage Collector Works — Detailed explanation
In the previous article, we aimed to delve deeper into what the Garbage Collector truly entails, questioning its necessity, examining its impact on performance, and highlighting its benefits alongside a basic understanding of object classification for garbage collection from the heap.
Building upon that foundation, this article will delve deeper into the automatic garbage collection process, exploring the generational garbage collection approach, the different types of collectors available, and how they genuinely aid in optimizing application performance.
What is Automatic Garbage Collection?
In Java, the Garbage Collector is a background daemon thread that automates the management of object lifecycles, alleviating the need for manual memory management found in languages like C. This system continuously scans the heap memory, identifying and deallocating memory from objects that no longer have active references, thus optimizing heap space utilization and enhancing application performance. The JVM handles these tasks automatically, running the garbage collection on a background thread to prevent disruption to the application’s execution, ensuring efficient memory management without requiring developers to manage memory allocation and deallocation manually.
An object becomes a candidate for garbage collection when no part of the program holds a reference to it anymore. The garbage collector functions periodically to identify and remove these unused objects through a multi-step process:
- Marking: The automatic garbage collection process begins by identifying all objects that are still in use. Starting from root references like global variables and method parameters, the garbage collector traces and marks all accessible objects. Those that are unreachable are slated for collection. This initial marking phase involves scanning all objects in the system — referenced objects are marked as in use, while unreferenced objects are marked for deletion. This comprehensive scanning can be time-consuming if the volume of objects is extensive.
- Sweeping: Following marking, the collector proceeds to sweep the heap, freeing up memory by deallocating space occupied by unmarked, hence unused, objects.
- Compacting: To optimize memory usage and reduce fragmentation, some garbage collection algorithms consolidate remaining objects. This process shifts objects to form a contiguous block of free memory, enhancing efficiency in future allocations. To optimize memory usage, the process may also include compacting, which consolidates all referenced objects. This rearrangement facilitates easier and quicker allocation of new objects by creating contiguous free memory blocks.
Why Generational Garbage Collection?
Having to mark and compact all the objects in a JVM is inefficient. As more and more objects are allocated, the list of objects grows and grows, leading to longer garbage collection times. However, empirical analysis of applications has shown that most objects are short-lived.
Here is an example of such data. The Y axis shows the number of bytes allocated, and the X access indicates the number of bytes allocated over time.
Java’s HotSpot VM employs generational garbage collection based on the “weak generational hypothesis” that most objects are short-lived. This method targets heap areas where objects quickly become unreachable, reducing garbage collection overhead. The generational hypothesis supports this, observing that most objects exhibit brief lifespans. HotSpot’s garbage collectors, tailored for different use cases, follow a uniform process: identifying and marking unreferenced objects, deleting marked objects, and optionally compacting memory by arranging remaining objects contiguously at the heap’s start. This process optimizes memory allocation and management, enhancing overall system performance.
The generational garbage collection process:
Java’s garbage collection architecture effectively manages memory by dividing the heap into strategic areas, each tailored to handle objects at different stages of their lifecycle. This structured approach helps optimize memory usage and enhances application performance by efficiently managing object allocation and garbage collection processes.
Stop the world event:
All Java garbage collectors perform “Stop the World” events, where the JVM pauses all application threads to carry out garbage collection tasks without interference safely. These pauses can impact application responsiveness, with their duration and frequency depending on the garbage collector used and its configuration.
Classification of Objects by the Garbage Collector:
Young Generation
The Young Generation is where new objects are allocated and is highly active in garbage collection. When it fills up, it triggers a minor garbage collection, which stops all application threads (Stop the World event) until completion. These collections are quick, assuming most objects will be discarded. Surviving objects are aged and eventually moved to the Old Generation.
Eden Space:
New objects are first allocated in the Eden Space. When the Eden Space becomes full, a minor garbage collection occurs:
- Clearing unused objects from the Eden Space.
- Moving surviving objects to the Survivor Spaces.
This process is designed for quick and frequent collections to handle high object turnover.
Survivor Spaces:
- Divided into two segments (S0 and S1), Survivor Spaces temporarily hold objects that survive garbage collection in the Eden Space. Objects may oscillate between these two areas with each minor GC cycle based on their age and activity, gradually aging until they are old enough to be moved to the Tenured Space. During minor GCs, referenced objects from Eden are moved to one of the Survivor Spaces, while referenced objects already in a Survivor Space are moved to the other, incrementing their age.
Old Generation (Tenured Space):
Objects that reach the age threshold in the Survivor Spaces are promoted to the Old Generation during minor GCs. The Old Generation stores long-lived objects promoted from the Young Generation. It undergoes major garbage collections, which are comprehensive “Stop the World” events. These collections are slower and involve all live objects, making it crucial to minimize their frequency for high responsiveness. The Old Generation is checked less frequently, reducing unnecessary checks but increasing the risk of memory leaks.
Permanent Generation (Metaspace):
In Java, the Permanent Generation, which stored metadata like classes and methods, was replaced by Metaspace from Java 8 onwards. Metaspace stores this class metadata in native memory and is part of the complete garbage collection process, removing unused classes. It is dynamically populated during runtime with application and Java SE library classes and methods. Unused classes are unloaded during full garbage collection events if they are no longer needed, freeing up space for other classes.
Types of Garbage Collectors:
There are two main types of Garbage Collection algorithms: Full Garbage Collection and Incremental Garbage Collection.
Full or Major Garbage Collection:
This method involves the garbage collector scanning all of a program’s memory to identify objects that are no longer in use, marking them as garbage eligible for removal. This comprehensive process is generally triggered when memory usage reaches a specific threshold or when a new memory request cannot be met due to lack of free space. Full garbage collection is common in languages with automatic memory management, like Java or Python, and typically requires pausing the program’s execution, which can temporarily slow down performance as it reclaims unused memory.
Incremental or Minor Garbage Collection:
Used primarily to manage memory in smaller, more manageable increments, this method involves the garbage collector periodically scanning the young generation’s heap memory for unreachable objects. Unlike full garbage collection, it doesn’t stop the program but segments the process into short phases or “increments” that handle different portions of memory one at a time. Each increment identifies and marks unneeded objects for reuse, thus allowing the program to continue running with minimal interruptions. This approach maintains the program’s responsiveness by avoiding long pauses, but it can be less efficient than other techniques like mark-and-sweep or generational garbage collection because it requires more frequent memory scans and incurs overhead from maintaining state information between increments.
Types of Garbage Collectors in Java:
Serial Garbage Collector
The Serial Garbage Collector (GC) is a single-threaded, stop-the-world garbage collector in Java, designed for client-style machines with small to medium-sized applications that don’t require high throughput or low pause times. It uses a mark-compact collection method, moving older objects to the beginning of the heap and allocating new objects in a continuous chunk at the end, improving memory allocation and management.
The Serial GC is suitable for applications that can tolerate stop-the-world events, where all application threads are paused during garbage collection. It’s particularly useful for:
- Client-style machines with a few hundred MBs of Java heap and relatively short worst-case pause times.
- Environments with multiple JVMs on a single machine, minimizing interference by using only one processor for garbage collection.
- Embedded systems with minimal memory and few cores, due to its simplicity and efficient memory management.
While offering manageable worst-case pause times for moderate heap sizes, the Serial GC may not be suitable for applications requiring high throughput or low pause times where other garbage collectors like Parallel or Concurrent Mark Sweep (CMS) GC might be more appropriate. It can be activated using the -XX:+UseSerialGC JVM argument.
Parallel Garbage Collector (Throughput Collector)
The Parallel Garbage Collector (GC) is a multi-threaded, stop-the-world garbage collector in Java, optimized for high throughput applications with large heaps. It’s the default garbage collector for server-style JVMs and leverages multiple CPUs to enhance garbage collection efficiency.
The Parallel GC uses multiple threads for both minor (young generation) and major (old generation) garbage collections concurrently. It’s particularly well-suited for:
- High-throughput applications that prioritize overall throughput over short pause times, like batch processing.
- Managing large heaps efficiently by utilizing multiple CPUs.
- Systems with multiple CPUs, significantly improving garbage collection performance.
Overall, the Parallel GC is a powerful choice for high-throughput applications that can tolerate longer pause times, particularly those running on systems with multiple CPUs and large heap requirements. It can be enabled using the -XX:+UseParallelGC option. However, the Parallel GC pauses all application threads during garbage collection phases (Stop the World events), which can be disruptive for applications requiring low pause times or strict real-time requirements.
It provides tuning options like -XX:ParallelGCThreads=<n> (sets GC threads), -XX:MaxGCPauseMillis=<t> (maximum pause time goal), and -XX:GCTimeRatio=<n> (throughput target).
Note: While it outperforms the Serial GC on systems with multiple CPUs, the Parallel GC might not perform as well on single-CPU systems due to the overhead associated with parallel execution and synchronization.
Concurrent Mark Sweep (CMS) Garbage Collector (low-pause collector)
The Concurrent Mark Sweep (CMS) collector is a low-pause garbage collector designed for applications that require short garbage collection pauses and can share CPU resources during runtime. It’s optimized for systems with significant long-lived data on multi-processor machines.
Get Sonisumit’s stories in your inbox
Join Medium for free to get updates from this writer.
CMS uses multiple threads to perform most garbage collection work concurrently with the application threads, minimizing disruption. However, it still needs to briefly pause application threads during the mark and sweep phases.
Minor collections of the young generation may also overlap and cause brief pauses during major CMS cycles. CMS can encounter a ‘concurrent mode failure’ if it fails to reclaim enough space in the old generation before it fills up, resorting to a full compacting collection with longer pauses.
CMS does not normally compact live objects in the old generation, and excessive fragmentation may require allocating a larger heap.
Proper configuration of CMS is crucial for applications prioritizing responsiveness and consistent throughput over raw throughput, such as desktop UIs, web servers, and databases responding to queries/events. To enable CMS, use the -XX:+UseConcMarkSweepGC flag. Tuning options include -XX:ParallelCMSThreads=<n> (sets CMS collector threads) and -XX:+UseParNewGC (uses the parallel collector for the young generation).
The Garbage-First (G1) Garbage Collector
The Garbage-First (G1) Garbage Collector, introduced in Oracle JDK 7 update 4, is tailored for server-style environments on multi-processor machines with large memory capacities. Designed to replace the Concurrent Mark-Sweep (CMS) collector, G1 is targeted at applications that require high throughput and predictable garbage collection (GC) pause times without compromising overall performance or requiring an excessively large Java heap.
Design and Operational Features:
Concurrent Operations: Similar to the CMS collector, G1 operates concurrently with application threads, minimizing pause times during memory management operations.
Region-Based Compaction: Unlike older garbage collectors that divided the heap into fixed young, old, and permanent generations, G1 partitions the heap into equal-sized regions. Apart from the Eden, Survivor, and Old Memory regions, there are two more types of areas present in the G1GC:
- Humongous — used for large-size objects (larger than 50% of heap size)
- Available — the unused or non-allocated space. This region-based approach allows more flexible memory usage and simplifies the garbage collection process by avoiding using fine-grained free lists for allocation, reducing potential fragmentation issues.
Predictable Pause Times: G1 provides more predictable garbage collection pauses compared to CMS, allowing users to specify desired pause duration targets. G1 uses a pause prediction model that selects the number of regions to collect based on these user-defined targets.
Garbage Collection Process:
- Initial Marking Phase: G1 performs a concurrent global marking phase to determine the liveness of objects across the heap. This phase identifies which regions are mostly empty and prioritizes them for collection, optimizing the process to focus on areas with the highest density of garbage.
- Evacuation Process: During garbage collection, G1 evacuates live objects from these prioritized regions to a single region, compacting and freeing up memory. This evacuation is executed in parallel on multi-processors, which enhances throughput and decreases pause times.
- Continuous Compaction: With each collection cycle, G1 continuously works to reduce fragmentation within the heap, maintaining efficiency and ensuring that the memory remains as contiguous as possible.
System Integration and Tuning:
G1 includes both concurrent phases (which run alongside application threads like refinement, marking, and cleanup) and parallel phases (multi-threaded operations typically inducing “stop the world” events). Although full garbage collections in G1 are single-threaded, proper tuning should minimize the need for these, as G1 is designed to manage memory efficiently enough to avoid full GCs under normal operational conditions.
Operational Advantages Over Previous Methods:
G1’s approach contrasts sharply with CMS, which does not perform compaction, and with the ParallelOld garbage collector, which only compacts the entire heap, leading to significant pause times. G1’s methodical compaction strategy ensures that only selected regions are compacted, allowing for more controlled pause times and improved memory management efficiency.
Limitations:
It is crucial to note that while G1 aims to meet set pause time targets, it does not guarantee these times with absolute certainty. The collector uses historical data to estimate how many regions can be effectively collected within the specified target times, creating a balanced model for ongoing memory management.
Overall, G1’s sophisticated design and operational features make it a robust solution for managing garbage collection in large-scale, server-style applications that run on multi-processor systems, providing a modern approach to memory management that adapts dynamically to the application’s needs.
Choosing the Appropriate Garbage Collector
For applications without stringent pause-time demands, the JVM’s default garbage collector settings are typically sufficient. However, if performance adjustments are needed, start by tweaking the heap size.
If further optimizations are necessary after adjusting the heap size, consider selecting a garbage collector that aligns with your specific application needs:
- Serial Collector: Ideal for applications with small data sets (up to around 100 MB) running on a single processor when pause times are not a concern.
- Parallel Collector: Suitable when peak performance is crucial and longer pause times (up to a second or more) are acceptable. Designed to enhance performance at the cost of longer garbage collection pauses.
- CMS/G1 Collector: Best when response time precedes maximum throughput. These collectors aim to keep garbage collection pauses under approximately one second, which is suitable for systems requiring quick responsiveness.
Strategies for Ensuring Java Objects Are Collected
- Scope-based Object Creation: Objects created within a method become eligible for garbage collection after the method execution completes.
- Nullifying References: Setting a reference variable to
nullbreaks the link to the object, making it eligible for collection if no other references exist. - Reassigning References: Assigning a new object to a reference variable makes the previously referenced object eligible for collection if no other references exist.
- Using Anonymous Objects: Anonymous objects are eligible for garbage collection once they go out of scope, as they have no accessible references.
Triggers for Java Garbage Collection
- Heap Space Allocation: The JVM performs garbage collection if there’s insufficient heap space for new objects.
- System.gc() Method Call: Developers can suggest garbage collection with
System.gc(), but it's not guaranteed to occur immediately. - Old Generation Threshold: Reaching a certain memory usage threshold in the old generation can trigger garbage collection.
- PermGen/Metaspace Threshold: Prior to Java 8, reaching a PermGen space threshold could trigger garbage collection. In Java 8 and later, it’s the Metaspace threshold.
- Time-Based Triggers: The JVM may collect garbage regularly, regardless of memory usage, to ensure continued efficiency.
Requesting JVM Garbage Collection
- Use System.gc(): This method signals the JVM to potentially initiate garbage collection.
- Use Runtime.getRuntime().gc(): This method functions similarly to
System.gc()but is less likely to be overridden by the JVM. - Set -XX:+DisableExplicitGC Flag: This JVM flag prevents garbage collection from being triggered by explicit calls to
System.gc()orRuntime.getRuntime().gc(), reducing unnecessary performance impacts.
Note: Manually requesting garbage collection is generally discouraged, as the JVM is designed to optimize memory management. Manual triggers can sometimes reduce performance instead of improving it, as they disrupt the JVM’s optimized garbage collection routine.
Are garbage collectors foolproof?
Java’s built-in Garbage Collector (GC) automates memory management, reducing the risk of memory leaks. However, the GC is not foolproof. Memory leaks can still occur if a program creates excessive and unnecessary objects that consume significant memory resources.
A memory leak in Java refers to objects that remain in the heap (Java’s memory pool) despite no longer being used by the application. The GC should ideally clean up these unreferenced objects, but if it doesn’t, they continue occupying memory, leading to wasted resources, performance deterioration, and potential java.lang.OutOfMemoryError crashes.
Memory leaks typically involve two types of objects in the heap: referenced and unreferenced. Referenced objects are those still connected to the application via active references, making them ineligible for garbage collection. On the other hand, Unreferenced objects have no active links and should be removed by the GC during its cleaning cycles.
Leaks arise when objects that should be unreferenced continue to be referenced, preventing the GC from freeing up their associated memory. This can happen due to programming errors or improper usage of certain language features.
While the GC handles memory management efficiently in most cases, it’s not a silver bullet. Memory leaks can still occur, requiring careful programming practices to avoid excessive and unnecessary object creation, proper object dereferencing, and judicious use of language features that can lead to unintended object referencing.
Conclusion:
I started this series to give back to the community and help you master Java, from the basics to the core fundamentals of the functioning of Java. With this article, I conclude the “How Java Works” series by completing the core functionality of the Garbage Collector, its performance impact, object classification for collection, the automatic process, generational approach, different collector types, and their role in optimization.
As we part ways, reflect on the knowledge gained, continue exploring Java, and share your thoughts — your feedback is invaluable.
Till then, Keep learning and stay tuned for more!
References:
- https://www.ibm.com/topics/garbage-collection-java
- https://www.baeldung.com/jvm-garbage-collectors
- https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
- https://stackify.com/what-is-java-garbage-collection/
- https://newrelic.com/blog/best-practices/java-garbage-collection
- https://www.freecodecamp.org/news/garbage-collection-in-java-what-is-gc-and-how-it-works-in-the-jvm/
- https://www.javatpoint.com/types-of-garbage-collector-in-java









