JVM GC Evolution
The JVM's GC algorithms have evolved over the past three decades. The very first GC algorithm shipped with the JVM was the serial collector. Initially, Java was popular on the client side (Java Applets), and the serial collector was perfect for its GC needs. However, the serial collector worked well only on small heaps and did not scale to large heaps. Furthermore, it did not take advantage of increasingly prevalent multi-core CPUs. As Java's popularity on the client side faded in the late 1990s and early 2000s, it became increasingly popular on the server side. This necessitated a new GC algorithm primarily catering to server-side applications.
The JVM introduced a parallel garbage collector to improve garbage collection performance in server-side applications. In contrast to the serial collector, the parallel collector divides the garbage collection process into separate workpieces and utilizes multiple garbage collection threads to handle each piece concurrently. Parallel garbage collection, also known as the throughput collector, represents a significant improvement over the serial garbage collector, particularly for JVMs with larger heaps deployed on multi-core CPUs. However, one of the main drawbacks of both serial and parallel collectors is the need for stop-the-world (STW) application pauses to collect garbage. The parallel collector's pause times increase linearly with heap size, meaning that STW pause times grow as the heap size expands.
Concurrent GC represents the next evolution in the JVM's GC algorithm space. This approach helps reduce garbage collection pause times, particularly for larger heaps. Concurrent GC takes advantage of available CPU cycles by performing garbage collection concurrently with application threads. With the increasing number of hardware threads/cores, concurrent GC has gained popularity as idle CPU cycles can be used effectively for garbage collection to reduce pause times. Concurrent GC has two main goals: concurrent marking and concurrent compacting. However, the latter is considerably more complex than the former.
Over the years, the JVM has introduced several concurrent garbage collectors. Concurrent Mark Sweep (CMS) was one of the first concurrent GC algorithms shipped on the HotSpot JVM. As the name suggests, CMS performs only concurrent marking and sweeping without concurrent compacting. CMS has two main shortcomings:
- CMS does not compact memory, which leads to heap fragmentation.
- CMS can be difficult to tune.
As memory became cheaper, heap sizes became enormous, necessitating a reorganization of the JVM heap to ensure minimal GC pauses with increasing heap sizes. To achieve this, the heap was divided into regions, and garbage collection was performed concurrently in the regions with the most garbage, following the garbage-first philosophy. G1GC was one of the first region-based GC algorithms. The primary goal of G1GC is to meet garbage collection pause-time objectives on large heaps with a high degree of certainty. Unlike CMS, G1GC provides compaction but does not perform concurrent heap compaction.
Shenandoah and ZGC represent the next evolution in the concurrent garbage collection space, intending to reduce GC pause times to less than 10ms. Currently, both of these GC implementations are non-generational and are among the first algorithms to provide concurrent compaction on the HotSpot JVM.
Azul's Zing was a revolutionary development in the GC space as it introduced a proprietary pauseless GC algorithm.
|Collector Name||Generational||Regional||YG Algo||YG Algo STW||OG Algo||OG Algo STW||Notes|
|Serial Collector||Yes||No||Single threaded/Mark and Copy||Yes||STW Mark Sweep Compact||Yes||Only recommended on tiny heaps and single-core machines. Virtually unused today.|
|Parallel Collector||Yes||No||Parallel/Mark and Copy||Yes||Parallel STW Mark and Compact||Yes||The most popular and widely used algorithm today. The default GC up to Java 8.|
|CMS Collector||Yes||No||Parallel/Mark and Copy||Yes||Concurrent Mark Sweep||No but does not compact||Does not compact and falls back to the parallel collector if allocation pressure is too high or the old gen is too fragmented.|
|G1GC||Yes||Yes||Parallel/Mark and Copy||Yes||Concurrent Mark and STW compacting||Mark and Sweep do not require STW pauses but compacting does require STW pauses||Falls back to using the parallel collector for an old generation if allocation pressure is too high.|
|Shenandoah||No||Yes||Concurrent Mark and compacting||NA||Concurrent Mark and compacting||No||Falls back to using its failure modes if allocation pressure is too high.|
|ZGC||Yes||Yes||Concurrent Mark and compacting||No||Concurrent Mark Sweep and compacting||No||Does not need any fallback algorithms|