This document will explain what is the garbage collector in java and the main types of garbage collectors with behaviors of each garbage collector. Also please note that In this article I’m not explaining how heap allocation happens and major/minor garbage collectors start. It will explain in the next article.
Java is an object-oriented programming language that includes Automatic Garbage Collection. Java automatically allocates and deallocates memory so programs are not burdened with that task. Now (Java-12), Java has seven types of garbage collectors,
- Serial Garbage Collector
- Parallel Garbage Collector
- CMS Garbage Collector
- G1 Garbage Collector
- Epsilon Garbage Collector
- Z garbage collector
- Shenandoah Garbage Collector
5th and 6th garbage collectors have introduced in java 11 and 7th one has introduced in java 12. In most applications in production has used the first four types of garbage collectors. Since the last three garbage collectors recently come into the picture.
Do we need to worry about GCs?
Yes, indeed. We have to worry about the GC and Its behaviors. Because It can provide a significant performance difference. Each GC has It’s own advantages and disadvantages. As developers, we have to have a clear idea about the behaviors of all garbage collectors and we have to select a garbage collector based on our business scenario. We can choose a garbage collector by, passing the choice as a JVM argument.
1. Serial Garbage Collector
This is the simplest GC implementation. It basically designed for a single thread environment. This GC implementation freezes all application threads when it runs. It uses single thread for garbage collection. Hence, it is not a good idea to use it in multi-threaded applications like server environments.
To enable Serial Garbage Collector, we can use the following argument:
java -XX:+UseSerialGC -jar Application.java
2. Parallel Garbage Collector
The parallel garbage collector is also called as a throughput collector. Unlike the serial garbage collector, this uses multiple threads for garbage collection. Similar to serial garbage collector this also freezes all the application threads while performing garbage collection. The garbage collector is suited best for those applications that can bear application pauses.
To enable parallel Garbage Collector, we can use the following argument:
java -XX:+UseParallelGC -jar Application.java
If we use this GC, we can specify maximum garbage collection threads and pause time, throughput and footprint (heap size)
The numbers of garbage collector threads can be controlled with the command-line option
The maximum pause time goal (gap [in milliseconds] between two GC)is specified with the command-line option
The maximum throughput target (measured regarding the time spent doing garbage collection versus the time spent outside of garbage collection) is specified by the command-line option
A maximum heap footprint (the amount of heap memory that a program requires while running) is specified using the option -Xmx<N>.
3. CMS Garbage Collector
Concurrent Mark Sweep (CMS) garbage collector uses multiple garbage collector threads for garbage collection. It scans the heap memory to mark instances for eviction and then sweep the marked instances. It’s designed for applications that prefer shorter garbage collection pauses, and that can afford to share processor resources with the garbage collector while the application is running.
CMS garbage collector holds all the application threads in the following two scenarios only
- During marking the referenced objects in the old generation space.
- Any change in heap memory in parallel with doing the garbage collection
In comparison with the parallel garbage collector, CMS collector uses more CPU to ensure better application throughput. If we can allocate more CPU for better performance then CMS garbage collector is the preferred choice over the parallel collector.
To enable CMS Garbage Collector, we can use the following argument:
java -XX:+USeParNewGC -jar Application.java
4. G1 Garbage Collector
G1 (Garbage First) Garbage Collector is designed for applications running on multi-processor machines with large memory space. It’s available since JDK7 Update 4 and in later releases.
It separates the heap memory into regions and does collection within them in parallel. G1 also does compacts the free heap space on the go just after reclaiming the memory. But CMS garbage collector compacts the memory on stop the world (STW) situations. G1 collector will replace the CMS collector since it’s more performance efficient.
In G1 collector contains two phases;
Unlike other collectors, G1 collector partitions the heap into a set of equal-sized heap regions, each a contiguous range of virtual memory. When performing garbage collections, G1 shows a concurrent global marking phase to determine the liveness of objects throughout the heap.
After the mark phase is completed, G1 knows which regions are mostly empty. It collects in these areas first, which usually yields a significant amount of free space. It is why this method of garbage collection is called Garbage-First.
To enable G1 Garbage Collector, we can use the following argument:
java -XX:+UseG1GC -jar Application.java
5. Epsilon Garbage Collector
Epsilon is a non-operational or a passive garbage collector. It allocates the memory for the application, but it doesn’t collect the unused objects. When the application exhausts the Java heap, the JVM shuts down. It means Epsilon garbage collector allows, applications to run out of memory and crash.
The purpose of this garbage collector is measuring and managing application performance. Active garbage collectors are complex programs that run inside the JVM alongside your application. Epsilon removes the impact GC has on performance. There are no GC cycles or read or write barriers. When using the Epsilon GC, the code runs in isolation. Epsilon helps to visualize how garbage collection affects the app’s performance and what are the memory threshold is since it’ll show when it runs out. As an example If we think we only need one gigabyte of memory for our application, we can run it with -Xmx1g and see the behavior. If that memory allocation is not sufficient, rerun it with a heap dump. Please note that we have to enable this option to get a heap dump. We can use this argument to get a heap dump when the application is crashing due to out of memory.
If we need to squeeze every bit of performance out of our application, Epsilon might be your best option for a GC. But we need to have a complete understanding of how our code uses memory. If it creates almost no garbage or you know exactly how much memory it uses for the period it runs in, Epsilon is a viable option.
To enable Epsilon Garbage Collector, we can use the following argument:
java -XX:+UseEpsilonGC -jar Application.java
6. Z garbage collector
ZGC performs all expensive work concurrently, without stopping the execution of application threads for more than 10ms, which makes it suitable for applications that require low latency and/or use a very large heap. According to the Oracle documentation, it can handle multi-terabyte heaps. Oracle introduced ZGC in Java 11. The Z garbage collector performs its cycles in its threads. It pauses the application for an average of 1 ms. The G1 and Parallel collectors average roughly 200 ms.
In Java 12, Oracle added performance fixes and class unloading even though Z is still in experimental status. It’s only available on 64-bit Linux. But, ZGC takes advantage of 64-bit pointers with a technique called pointer coloring. Colored pointers store extra information about objects on the heap. This is one of the reasons it’s limited to the 64-bit JVM. This article has been explained this scenario deeply (https://www.opsian.com/blog/javas-new-zgc-is-very-exciting/).
ZGC does its marking in three phases.
1. Short stop-the-world phase — It examines the GC roots, local variables that point to the rest of the heap. The total number of these roots is usually minimal and doesn’t scale with the size of the load, so ZGC’s pauses are very short and don’t increase as your heap grows.
2. Concurrent phase — It walks the object graph and examines the colored pointers, marking accessible objects. The load barrier prevents contention between the GC phase and any application’s activity.
3. Relocation phase — It moves live objects to free up large sections of the heap to make allocations faster. When the relocation phase begins, ZGC divides the heap into pages and works on one page at a time. Once ZGC finishes moving any roots, the rest of the relocation happens in a concurrent phase.
ZGC will try to set the number of threads itself, and it’s usually right. But if ZGC has too many threads, it will starve your application. If it doesn’t have enough, you’ll create garbage faster than the GC can collect it. ZGC’s phases illustrate how it manages large heaps without impacting performance as application memory grows.
To enable Z Garbage Collector, we can use the following argument:
java -XX:+UseZGC -jar Application.java
Shenandoah is an ultra-low pause time garbage collector that reduces GC pause times by performing more garbage collection work concurrently with the running Java program. CMS and G1 both perform concurrent marking of live objects. Shenandoah adds concurrent compaction.
Shenandoah uses memory regions to manage which objects are no longer in use and which are live and ready for compression. Shenandoah also adds a forwarding pointer to every heap object and uses it to control access to the object. Shenandoah’s design trades concurrent CPU cycles and space for pause time improvements. The forwarding pointer makes it easy to move objects, but the aggressive moves mean Shenandoah uses more memory and requires more parallel work than other GCs. But it does the extra work with very brief stop-the-world pauses.
Shenandoah processes the heap in many small phases, most of which are concurrent with the application. This design makes it possible for the GC to manage a large heap efficiently.
- First stop-the-world pause in the cycle. It prepares the heap for concurrent marking and scans the root set. LikeZGC, the length of this pause corresponds to the size of the root set, not the heap.
- Next, a concurrent phase walks the heap and identifies reachable and unreachable objects.
- The third finishes the process of marking by draining pending heap updates and re-scanning the root set. This phase triggers the second stop-the-world pause in the cycle. The number of pending updates and the size of the root set determine how long the pause is.
- Then, another concurrent phase copies the objects out of the regions identified in the final mark phase. This process sets Shenandoah apart from other GCs since it aggressively compacts the heap in parallel with application threads.
- The next phase triggers the third (and shortest) pause in the cycle. It ensures that all GC threads have finished evacuation.
- When it finishes, a concurrent phase walks the heap and updates references to objects moved earlier in the cycle.
- The last stop-the-world pause in the cycle finishes updating the references by updating the root set. At the same time, it recycles the evacuated regions.
- Finally, the last phase reclaims the evacuated regions, which now have no references in them.
We can configure Shenandoah with one of three heuristics. They govern when the GC starts its cycles and how it selects regions for evacuation.
1. Adaptive: Observes GC cycles and starts the next cycle so it completes before the application exhausts the heap. This heuristic is the default mode.
2. Static: Starts a GC cycle based on heap occupancy and allocation pressure.
3. Compact: Runs GC cycles continuously. Shenandoah starts a new cycle as soon as the previous finishes or based on the amount of heap-allocated since the last cycle. This heuristic incurs throughput overhead but provides the best space reclamation.
Shenandoah needs to collect heap faster than the application it’s serving allocates it. If the allocation pressure is too high and there’s not enough space for new allocations, there will be a failure. Shenandoah has configurable mechanisms for this situation.
- Pacing: If Shenandoah starts to fall behind the rate of allocation, it will stall allocation threads to catch up. The stalls are usually enough for mild allocation spikes. Shenandoah introduces delays of 10ms or less. If pacing fails, Shenandoah will move to the next step: degenerated GC.
- Degenerated GC: If an allocation failure occurs, Shenandoah starts a stop-the-world phase. It uses the phase to complete the current GC cycle. Since a stop-the-world doesn’t contend with the application for resources, the cycle should finish quickly and clear the allocation shortfall. Often, a degenerated cycle happens after most of the cycle’s work is already completed, so the stop-the-world is brief. The GC log will report it as a full pause, though.
- Full GC: If both pacing and a degenerated GC fail, Shenandoah falls back to a full GC cycle. This final GC guarantees the application won’t fail with an out-of-memory error unless there’s no heap left.
Shenandoah offers the same advantages as ZGC with large heaps but more tuning options. Depending on the nature of your application, the different heuristics may be a good fit. Its pause times might not be as brief as ZGC’s, but they’re more predictable.
To enable the Shenandoah Garbage Collector, we can use the following argument:
java -XX:+UseShenanodoahC -jar Application.java
The whole purpose of this article is to summarize all the garbage collectors. Hence some content parts have been extracted from the given references. We have to have a clear idea about the garbage collectors to select an optimal garbage collector for our application use cases. The optimal garbage collector will be improved our application performance significantly.