This is how garbage collection works. While some nod to JVM versions is given, most of "the way it was" is sacrificed in these notes and the assumption is made that Java 8 or later is the version. For historical diversion, please read someone else's articles. They abound anyway.
The JVM heap is divided into two generations:
The young generation is futher subdivided into two sections:
(There are also virtual spaces for both generations and used by garbage collectors to resize other regions.)
I found and stole this illustration from Grzegorz Mirek:
Objects are usually created and used for short periods of time. This maxime underpins the weak-generational hypothesis, a theory of garbage collection.
Objects begin their life in Eden (hence the name). When Eden fills up, a so-called minor garbage collection is done. This is disruptive:
Why toss objects back and forth between S1 and S0? When the object reaches a measurable threshold, i.e.: a configured number of times tossed between S0 and S1, then it's promoted to old or tenured generation for more permanent location (since it's "proved" itself to be less likely to be in need of being collected). This is called the tenuring threshold: if a young-generation region survives and retains enough live objects to avoid being evacuated (moved out of the region with the goal that the region will then be considered free), then this region is promoted, first to survivor, then eventually to tenured.
Ultimately, old generation will fill up at which time major garbage collection will be performed to clean up and compact that space. It's how major garbage collection is done, how stop-the-world pauses occur, that depends on the specific garbage-collection algorithm chosen.
Beside minor and major garbage collection, there is also a full garbage collection that's all about cleaning up the entire heap (both young and old generations). Full garbage collection is triggered by minor garbage collection. For this reason, what's formally termed major garbage collection in the explanation above is often passed upon in silence and full garbage collection is discussed instead.
There are two advantages to dividing the heap into two regions as described. First, it's always faster to have to process only some portion of the heap (one of the two divisions). That way, the infamous stop-the-world pauses are greatly reduced in number and in length of interruption. Second, during minor garbage collection, all objects from Eden are either moved or discarded. This automatically means that Eden remains a part of the heap that is always compacted (i.e.: not fragmented).
To put it another way, evacuation failures (when it's not possible to move a region out so that an entire region is left free) happen when there aren't any free regions left in the collector. No free regions means nowhere to evacuate objects. This is why G1GC attempts to compact its regions on the fly instead of waiting for a compaction failure to occur.
There are three:
This type is performed by only one thread. This necessitates stop-the-world pauses during both minor and full garbage collection. (Remember that "full" in some discussions often means major as well as full.) The mark/copy algorithm is used for young generation whereas the mark/sweep/compact algorithm is used for old generation.
This type of garbage collection is designed for single-threaded environments, usually client-class hosts, and for relatively small heaps. It's enabled using the -XX:+UseSerialGC option to the JVM.
The young generation collection is parallelized by multiple threads making minor garbage collection much faster. This leads to short if more frequent stop-the-world pauses. Since Java 7, old generation is collected this way too (and suffers stop-the-world pauses). Both -XX:+UseParalletGC and -XX:+UseParallelOldGC (since "old" means "pre-Java 7") enable parallel garbage collection.
Parallel collection also uses the mark/copy algorithm for young generation and mark/sweep/compact for old generation. Both are executed by multiple threads as noted. To configure the number of threads, use the -XX:ParallelGCThreads=N option.
Parallel is a good choice whenever throughput is more important than reduced latency.
This sort of garbage collection is designed to minimize stop-the-world pauses, that is, reducing latency, making the application run as responsively as possible. Applications using it prefer short garbage-colleciton pauses and can afford to share resources with the garbage collector while running. If an application has a relatively large set of long-lived data (a large, tenured generation) and is run on machines with two or more processors, it will benefit from this collect. However, once you start using heaps in the 10s or 100s of gigabytes, theGC pause times start to ramp up seriously.
In this type of garbage collection, minor garbage collection is done using multiple threads and a parallel mark/copy algorithm. All application threads are halted during minor garbage collection. Old generation is collected concurrently (application threads are paused for very short periods of time as the background thread scans the space).
The first pause is to mark as live the ojbects directly reachable from the roots (for example, object references from application-thread stacks and registers, static objects, etc.) and from elsewhere in the heap (for example, the young generation). The first pause is called initial mark pause. The second pause comes at the end of the concurrent tracing phase and finds objects that were missed by the concurrent tracing due to updates by the application threads of references in an object after the collector had finished tracing it. This second pause is called the remark pause.
During major garbage collection, concurrent mark/sweep is the algorithm. There's no compact after the sweep and the old (or tenured) space isn't compacted, the memory is left fragmented.
When garbage collection isn't able to fit new objects in memory, because of no compaction, the JVM falls back to the serial mark/sweep/compact algorithm to defragment and and compact old generation and performace degradation occurs with all application threads being halted while a single thread carries out the work on the old space.
This collector throws an OutOfMemoryError if too much time is spent in garbage colleciton: if more than 98% of the time is spent in garbage collection while less than 2% of the heap is recovered. This is to prevent applications from running for an extended period of time while making little or no progress because the heap is so small. This behavior can be disabled: -XX:-UseGCOverheadLimit.
This type of collection is done using the -X:+UseConcMarkSweepGC option.
Beware: many early articles on G1GC speak in consequence of problems this method had before it had evolved long enough to become the default method for Java 9. Not every observed, bad behavior you read remained true for G1GC.
Older types of garbage collection, that might have resulted in longer, stop-the-world pauses work tolerably for service-oriented architecture applications, but not for services (especially clustered applications like Cassandra and Kafka). Indeed, if an application has to stop completely for longer than a few milliseconds, this will likely become a very serious problem for services with a heart beat, gossip contact, etc.
The goal of G1GC is to mark actively regions continually and put off full GC like CMS, then run full GC on a single thread. It's observed that full GC is far less work (than in parallel GC's case). So, average throughput is comparable to parallel GC while not stopping-the-world as does parallel GC and then, when resorting to stopping it, the pause isn't nearly as long as parallel GC. Also, G1GC supports enormous heaps sizes (for example up to 32Gb) as compared to parallel GC which, for the same heap size, takes a very long time to complete full GC.
Specially, and something I use for NiFi and generally recommend (not that I'm any authority—it's just my practice to do so), is garbage-first garbage collection (G1GC). This breaks the heap down into several regions of fixed size (if still maintaining the generational nature of the heap as a whole). This design rids garbage collection of long, stop-the-world pauses for both young and old generation spaces. Each region is collected separately. Objects are copied from one region to another (regions do not map to generation spaces). This produces a heap that is at all times at least partially compacted. G1GC uses an incremental version of the mark/sweep/compact algorithm. It's enabled using the -XX:+UseG1GC option.
G1GC is an option for Java 8 (parallel is the default for server-class hosts). It is the default in Java 9. All client-class hosts continue to run serial by default.
How to know when you need to tune G1GC differently from its defaults? G1GC is intended to avoid full GC as much as possible. It's recommended that if you detect any full GC pauses, you should tune.
Tuning can be tedious, complex and even mysterious, but there's help in a tool at GCEasy. You only need to create a garbage-collection log via this JVM command-line option:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:GC-log-file-path...then up-load the resulting log file to GCEasy (if very big, a zipped copy will make the process faster).
On the eve of JDK 9's release, the following options will no longer be supported:
|DefNew + CMS||-XX:-UseParNewGC -XX:+UseConcMarkSweepGC|
|ParNew + SerialOld||-XX:+UseParNewGC|
|ParNew + iCMS||-Xincgc|
|ParNew + iCMS||-XX:+CMSIncrementalMode -XX:+UseConcMarkSweepGC|
|DefNew + iCMS||-XX:+CMSIncrementalMode -XX:+UseConcMarkSweepGC -XX:-UseParNewGC|
Options for tuning mixed garbage collection, that is, organizing both young- and old-generation collections.