CMS Collector and G1 Collector

Understanding the CMS Collector

CMS has three basic operations:

  • CMS collects the young generation (stopping all application threads).
  • CMS runs a concurrent cycle to clean data out of the old generation.
  • If necessary, CMS performs a full GC.

concurrent cycle

  • The JVM starts a concurrent cycle based on the occupancy of the heap. When it is 
    sufficiently full, the JVM starts background threads that cycle through the heap and 
    remove objects.
  • The concurrent cycle starts with an initial mark phase, which stops all the application 
    threads.This phase is responsible for finding all the GC root objects in the heap.
  • The next phase is the mark phase, and it does not stop the application threads.Since it is just a 
    marking phase, it hasn’t done anything to the heap occupancy, and so no data is shown 
    about that.
  • Next comes a preclean phase, which also runs concurrently with the application threads:
  • The next phase is a remark phase, but it involves several operations:
  • Next comes another concurrent phase—the sweep phase:
  • Next comes the concurrent reset phase:

concurrent mode failure:

  • When a young collection occurs and there isn’t enough room in the old generation to 
    hold all the objects that are expected to be promoted, CMS executes what is essentially 
    a full GC. All application threads are stopped, and the old generation is cleaned of any 
    dead objects, reducing its occupancy to 1,366 MB—an operation which kept the application 
    threads paused for a full 5.6 seconds. That operation is single-threaded, which is one reason it takes so long (and one reason why concurrent mode failures are worse as 
    the heap grows).
  • The second problem occurs when there is enough room in the old generation to hold 
    the promoted objects, but the free space is fragmented and so the promotion fails: 
    • As a result, in the middle of the young collection (when all threads were already stopped), 
      CMS collected and compacted the entire old generation. The good news is that 
      with the heap compacted, fragmentation issues have been solved (at least for a while). 
      But that came with a hefty 28-second pause time. This time is much longer than when 
      CMS had a concurrent mode failure because the entire heap was compacted; the concurrent 
      mode failure simply freed objects in the heap. The heap at this point appears as 
      it did at the end of the throughput collector’s full GC (Figure 6-2): the young generation 
      is completely empty, and the old generation has been compacted.
    • This occurs when permgen has filled up and needs to be collected; notice that the size 
      of the CMS Perm space has dropped. In Java 8, this can also occur if the metaspace needs 
      to be resized. By default, CMS does not collect permgen (or the metaspace), so if it fills 
      up, a full GC is needed to discard any unreferenced classes.

summary

  • CMS has several GC operations, but the expected operations are 
    minor GCs and concurrent cycles.
  • Concurrent mode failures and promotion failures in CMS are 
    quite expensive; CMS should be tuned to avoid these as much as 
    possible.
  • By default, CMS does not collect permgen.

Understanding the G1 Collector

  • G1 is a concurrent collector that operates on discrete regions within the heap. Each 
    region (there are by default around 2,048 of them) can belong to either the old or new 
    generation, and the generational regions need not be contiguous. The idea behind having 
    regions in the old generation is that when the concurrent background threads look 
    for unreferenced objects, some regions will contain more garbage than other regions. 
    The actual collection of a region still requires that application threads be stopped, but 
    G1 can focus on the regions that are mostly garbage and only spend a little bit of time 
    emptying those regions. This approach—clearing out only the mostly garbage regions 
    —is what gives G1 its name: Garbage First.
  • That doesn’t apply to the regions in the young generation: during a young GC, the entire 
    young generation is either freed or promoted (to a survivor space or to the old generation). 
    Still, the young generation is defined in terms of regions, in part because it makes 
    resizing the generations much easier if the regions are predefined. 
    G1 has four main operations: 
    • A young collection
    • A background, concurrent cycle
    • A mixed collection
    • If necessary, a full GC

young collection

  • The G1 young collection is triggered when eden fills up (in this case, after filling four 
    regions). After the collection, there are no regions assigned to eden, since it is empty. 
    There is at least one region assigned to the survivor space (partially filled in this example), 
    and some data has moved into the old generation.
  • 23.430: [GC pause (young), 0.23094400 secs] 
    … 
    [Eden: 1286M(1286M)->0B(1212M) 
    Survivors: 78M->152M Heap: 1454M(4096M)->242M(4096M)] 
    [Times: user=0.85 sys=0.05, real=0.23 secs]
  • Collection of the young generation took 0.23 seconds of real time, during which the GC 
    threads consumed 0.85 seconds of CPU time. 1,286 MB of objects were moved out of 
    eden (which was resized to 1,212 MB); 74 MB of that was moved to the survivor space 
    (it increased in size from 78 M to 152 MB) and the rest were freed. We know they were 
    freed by observing that the total heap occupancy decreased by 1,212 MB. In the general 
    case, some objects from the survivor space might have been moved to the old generation, 
    and if the survivor space were full, some objects from eden would have been promoted 
    directly to the old generation—in those cases, the size of the old generation would 
    increase.

concurrent G1 cycle

  • Finally, notice that the old generation (consisting of the regions marked with an O or 
    an X) is actually more occupied after the cycle has completed. That’s because the young 
    generation collections that occurred during the marking cycle promoted data into the old generation. In addition, the marking cycle doesn’t actually free any data in the old 
    generation: it merely identifies regions that are mostly garbage. Data from those regions 
    is freed in a later cycle.
  • The G1 concurrent cycle has several phases, some of which stop all application threads 
    and some of which do not. The first phase is an initial-mark phase. That phase stops all 
    application threads—partly because it also executes a young collection: 
    • As in a regular young collection, the application threads were stopped (for 0.28 seconds), 
      and the young generation was emptied (71 MB of data was moved from the young 
      generation to the old generation). The initial-mark output announces that the background 
      concurrent cycle has begun. Since the initial mark phase also requires all application 
      threads to be stopped, G1 takes advantage of the young GC cycle to do that 
      work. The impact of adding the initial mark phase to the young GC wasn’t that large: it 
      used 20% more CPU cycles than the previous collection, even though the pause was 
      only slightly longer. (Fortunately, there were spare CPU cycles on the machine for the 
      parallel G1 threads, or the pause would have been longer.)
  • Next, G1 scans the root region: 
    • 350.994: [GC pause (young) 
      351.093: [GC concurrent-root-region-scan-end, 0.6100090] 
      351.093: [GC concurrent-mark-start], 
      0.37559600 secs]
    • This takes 0.58 seconds, but it doesn’t stop the application threads; it only uses the 
      background threads. However, this phase cannot be interrupted by a young collection, 
      so having available CPU cycles for those background threads is crucial. If the young 
      generation happens to fill up during the root region scanning, the young collection 
      (which has stopped all the application threads) must wait for the root scanning to complete. 
      In effect, this means a longer-than-usual pause to collect the young generation.
    • The GC pause here starts before the end of the root region scanning, which (along with 
      the interleaved output) indicates that it was waiting. The timestamps show that application 
      threads waited about 100 ms—which is why the duration of the young GC pause 
      is about 100 ms longer than the average duration of other pauses in this log.
  • After the root region scanning, G1 enters a concurrent marking phase. This happens 
    completely in the background; a message is printed when it starts and ends: 
    • 111.382: [GC concurrent-mark-start] 
      …. 
      120.905: [GC concurrent-mark-end, 9.5225160 sec]
  • Concurrent marking can be interrupted, so young collections may occur during this 
    phase. The marking phase is followed by a remarking phase and a normal cleanup phase: 
    • 120.910: [GC remark 120.959: 
      [GC ref-PRC, 0.0000890 secs], 0.0718990 secs] 
      [Times: user=0.23 sys=0.01, real=0.08 secs] 
      120.985: [GC cleanup 3510M->3434M(4096M), 0.0111040 secs] 
      [Times: user=0.04 sys=0.00, real=0.01 secs]
  • These phases stop the application threads, though usually for a quite short time. Next 
    there is an additional cleanup phase that happens concurrently: 
    • 120.996: [GC concurrent-cleanup-start] 
      120.996: [GC concurrent-cleanup-end, 0.0004520]
  • And with that, the normal G1 cycle is complete—insofar as finding the garbage goes, at 
    least. But very little has actually been freed yet. A little memory was reclaimed in the 
    cleanup phase, but all G1 has really done at this point is to identify old regions that are 
    mostly garbage and can be reclaimed (the ones marked with an X in Figure 6-7).

mixed GCs.

  • Now G1 executes a series of mixed GCs. They are called mixed because they perform 
    the normal young collection, but they also collect some number of the marked regions 
    from the background scan.
  • As is usual for a young collection, G1 has completely emptied eden and adjusted the 
    survivor spaces. Additionally, two of the marked regions have been collected. Those 
    regions were known to contain mostly garbage, and so a large part of them was freed. Any live data in those regions was moved to another region (just as live data was moved 
    from the young generation into regions in the old generation). This is why G1 ends up 
    with a fragmented heap less often than CMS—moving the objects like this is compacting 
    the heap as G1 goes along.
  • 79.826: [GC pause (mixed), 0.26161600 secs] 
    …. 
    [Eden: 1222M(1222M)->0B(1220M) 
    Survivors: 142M->144M Heap: 3200M(4096M)->1964M(4096M)] 
    [Times: user=1.01 sys=0.00, real=0.26 secs]
  • Notice that the entire heap usage has been reduced by more than just the 1,222 MB 
    removed from eden. That difference (16 MB) seems small, but remember that some of 
    the survivor space was promoted into the old generation at the same time; in addition, 
    each mixed GC cleans up only a portion of the targeted old generation regions. As we 
    continue, we’ll see that it is important to make sure that the mixed GCs clean up enough 
    memory to prevent future concurrent failures.
  • The mixed GC cycles will continue until (almost) all of the marked regions have been 
    collected, at which point G1 will resume regular young GC cycles. Eventually, G1 will 
    start another concurrent cycle to determine which regions should be freed next.

full GC

  • As with CMS, there are times when you’ll observe a full GC in the log, which is an 
    indication that more tuning (including, possibly, more heap space) will benefit the application 
    performance. There are primarily four times when this is triggered:
  • Concurrent mode failure 
    • G1 starts a marking cycle, but the old generation fills up before the cycle is completed. 
      In that case, G1 aborts the marking cycle:
    • 51.408: [GC concurrent-mark-start] 
      65.473: [Full GC 4095M->1395M(4096M), 6.1963770 secs] 
      [Times: user=7.87 sys=0.00, real=6.20 secs] 
      71.669: [GC concurrent-mark-abort]
    • This failure means that heap size should be increased, or the G1 background processing 
      must begin sooner, or the cycle must be tuned to run more quickly (e.g., by 
      using additional background threads).
  • Promotion failure 
    • G1 has completed a marking cycle and has started performing mixed GCs to clean 
      up the old regions, but the old generation runs out of space before enough memory 
      can be reclaimed from the old generation. In the log, a full GC immediately follows 
      a mixed GC:
    • 2226.224: [GC pause (mixed) 
      2226.440: [SoftReference, 0 refs, 0.0000060 secs] 
      2226.441: [WeakReference, 0 refs, 0.0000020 secs] 
      2226.441: [FinalReference, 0 refs, 0.0000010 secs] 
      2226.441: [PhantomReference, 0 refs, 0.0000010 secs] 
      2226.441: [JNI Weak Reference, 0.0000030 secs] 
      (to-space exhausted), 0.2390040 secs] 
      …. 
      [Eden: 0.0B(400.0M)->0.0B(400.0M) 
      Survivors: 0.0B->0.0B Heap: 2006.4M(2048.0M)->2006.4M(2048.0M)] 
      [Times: user=1.70 sys=0.04, real=0.26 secs] 
      2226.510: [Full GC (Allocation Failure) 
      2227.519: [SoftReference, 4329 refs, 0.0005520 secs] 
      2227.520: [WeakReference, 12646 refs, 0.0010510 secs] 
      2227.521: [FinalReference, 7538 refs, 0.0005660 secs] 
      2227.521: [PhantomReference, 168 refs, 0.0000120 secs] 
      2227.521: [JNI Weak Reference, 0.0000020 secs] 
      2006M->907M(2048M), 4.1615450 secs] 
      [Times: user=6.76 sys=0.01, real=4.16 secs]
    • This failure means the mixed collections need to happen more quickly; each young 
      collection needs to process more regions in the old generation.
  • Evacuation failure 
    • When performing a young collection, there isn’t enough room in the survivor 
      spaces and the old generation to hold all the surviving objects. This appears in the 
      GC logs as a specific kind of young GC:
    • 60.238: [GC pause (young) (to-space overflow), 0.41546900 secs]
    • This is an indication that the heap is largely full or fragmented. G1 will attempt to 
      compensate for this, but you can expect this to end badly: G1 will resort to performing 
      a full GC. The easy way to overcome this is to increase the heap size,
  • Humongous allocation failure 
    • Applications that allocate very large objects can trigger another kind of full GC in 
      G1; see “G1 allocation of humongous objects” on page 169. There are no tools to 
      diagnose that situation specifically from the standard GC log, though if a full GC 
      occurs for no apparent reason, it is likely due to an issue with humongous allocations.

Quick Summary

  • G1 has a number of cycles (and phases within the concurrent 
    cycle). A well-tuned JVM running G1 should only experience 
    young, mixed, and concurrent GC cycles.
  • Small pauses occur for some of the G1 concurrent phases.
  • G1 should be tuned if necessary to avoid full GC cycles.

posted @ 2018-09-23 00:51  parkdifferent  阅读(268)  评论(0编辑  收藏  举报