CMS vs G1基本原理總結(jié)

CMS

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。這是因?yàn)镃MS收集器工作時(shí),GC工作線程與用戶線程可以并發(fā)(Concurrent)執(zhí)行,以此來達(dá)到降低收集停頓時(shí)間的目的

CMS GC步驟

初始標(biāo)記(STW initial mark)

  1. 標(biāo)記GC-Root 直接引用的老年代對象

  2. 年輕代Live對象直接引用的老年代對象(CMS 初始標(biāo)記需要遍歷年輕代,gc root除了一般定義的那些節(jié)點(diǎn)外,還需要加上從年輕代到老年代的引用)

https://www.zhihu.com/question/53613423/answer/135743258 一組活躍對象R大解釋

分代式GC是一種部分收集(partial collection)的做法。在執(zhí)行部分收集時(shí),從GC堆的非收集部分指向收集部分的引用,也必須作為GC roots的一部分。

GC ROOTS=normal gc roots+(非收集指向收集部分的引用)官方解釋 https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html

## Pauses
The CMS collector pauses an application twice during a concurrent 
collection cycle. The first pause is to mark as live the objects directly 
reachable from the roots (for example, object references from application 
thread stacks and registers, static objects and so on) and from elsewhere 
in the heap (for example, the young generation). This first pause is 
referred to as the *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 CMS collector had finished tracing that 
object. This second pause is referred to as the *remark pause*.
image

參見深入理解JVM 第三版 CMS并未記錄年輕代到老年代的代際引用

image

并發(fā)標(biāo)記(Concurrent marking)

并發(fā)標(biāo)記階段的主要工作是, 通過遍歷第一個(gè)階段(Initial Mark)標(biāo)記出來的存活對象,繼續(xù)遞歸遍歷老年代,并標(biāo)記可直接或間接到達(dá)的所有老年代存活對象 。(Current obj 對象在并發(fā)標(biāo)記的同時(shí)被應(yīng)用程序修改了引用關(guān)系,所以這里會(huì)被標(biāo)記為dirty card)

不過card table還有其他作用。

進(jìn)行Minor GC時(shí),如果有老年代引用新生代,怎么識(shí)別?

當(dāng)有老年代引用新生代,對應(yīng)的card table被標(biāo)識(shí)為相應(yīng)的值(card table中是一個(gè)byte,有八位,約定好每一位的含義就可區(qū)分哪個(gè)是引用新生代,哪個(gè)是并發(fā)標(biāo)記階段修改過的)。P172 The Garbage Collection Handbook

image

并發(fā)預(yù)清理(Concurrent precleaning)

在并發(fā)預(yù)清洗階段, 將會(huì)重新掃描前一個(gè)階段標(biāo)記的Dirty對象,并標(biāo)記被Dirty對象直接或間接引用的對象,然后清除Card標(biāo)識(shí) 。

image
image

可終止的預(yù)處理

該階段不是一定會(huì)發(fā)生,但很重要,相對也比較復(fù)雜!該階段發(fā)生的前提是,新生代Eden區(qū)的內(nèi)存使用量大于參數(shù)CMSScheduleRemarkEdenSizeThreshold 默認(rèn)是2M,如果新生代的對象太少,就沒有必要執(zhí)行該階段,直接執(zhí)行重新標(biāo)記階段。

為什么需要這個(gè)階段,存在的價(jià)值是什么?

目標(biāo):因?yàn)镃MS GC的終極目標(biāo)是降低垃圾回收時(shí)的暫停時(shí)間(STW),所以在該階段要盡最大的努力去處理那些在并發(fā)階段被應(yīng)用線程更新的老年代對象,這樣在暫停的重新標(biāo)記階段就可以少處理一些,暫停時(shí)間也會(huì)相應(yīng)的降低。

在該階段,主要循環(huán)的做兩件事:

1)處理 From 和 To 區(qū)的對象,標(biāo)記可達(dá)的老年代對象

2)和上一個(gè)階段一樣,掃描處理Dirty Card中的對象

當(dāng)然了,這個(gè)邏輯不會(huì)一直循環(huán)下去,打斷這個(gè)循環(huán)的條件有兩個(gè):

// Try and schedule the remark such that young gen
// occupancy is CMSScheduleRemarkEdenPenetration %.
void CMSCollector::abortable_preclean() {
  check_correct_thread_executing();
  assert(CMSPrecleaningEnabled,  "Inconsistent control state");
  assert(_collectorState == AbortablePreclean, "Inconsistent control state");

  // If Eden's current occupancy is below this threshold,
  // immediately schedule the remark; else preclean
  // past the next scavenge in an effort to
  // schedule the pause as described avove. By choosing
  // CMSScheduleRemarkEdenSizeThreshold >= max eden size
  // we will never do an actual abortable preclean cycle.
  if (get_eden_used() > CMSScheduleRemarkEdenSizeThreshold) {
    TraceCPUTime tcpu(PrintGCDetails, true, gclog_or_tty);
    CMSPhaseAccounting pa(this, "abortable-preclean", _gc_tracer_cm->gc_id(), !PrintGCDetails);
    // We need more smarts in the abortable preclean
    // loop below to deal with cases where allocation
    // in young gen is very very slow, and our precleaning
    // is running a losing race against a horde of
    // mutators intent on flooding us with CMS updates
    // (dirty cards).
    // One, admittedly dumb, strategy is to give up
    // after a certain number of abortable precleaning loops
    // or after a certain maximum time. We want to make
    // this smarter in the next iteration.
    // XXX FIX ME!!! YSR
    size_t loops = 0, workdone = 0, cumworkdone = 0, waited = 0;
    while (!(should_abort_preclean() ||
             ConcurrentMarkSweepThread::should_terminate())) {
      workdone = preclean_work(CMSPrecleanRefLists2, CMSPrecleanSurvivors2);
      cumworkdone += workdone;
      loops++;
      // Voluntarily terminate abortable preclean phase if we have
      // been at it for too long.
      if ((CMSMaxAbortablePrecleanLoops != 0) &&
          loops >= CMSMaxAbortablePrecleanLoops) {
        if (PrintGCDetails) {
          gclog_or_tty->print(" CMS: abort preclean due to loops ");
        }
        break;
      }
      if (pa.wallclock_millis() > CMSMaxAbortablePrecleanTime) {
        if (PrintGCDetails) {
          gclog_or_tty->print(" CMS: abort preclean due to time ");
        }
        break;
      }
      // If we are doing little work each iteration, we should
      // take a short break.
      if (workdone < CMSAbortablePrecleanMinWorkPerIteration) {
        // Sleep for some time, waiting for work to accumulate
        stopTimer();
        cmsThread()->wait_on_cms_lock(CMSAbortablePrecleanWaitMillis);
        startTimer();
        waited++;
      }
    }
    if (PrintCMSStatistics > 0) {
      gclog_or_tty->print(" [%d iterations, %d waits, %d cards)] ",
                          loops, waited, cumworkdone);
    }
  }
  CMSTokenSync x(true); // is cms thread
  if (_collectorState != Idling) {
    assert(_collectorState == AbortablePreclean,
           "Spontaneous state transition?");
    _collectorState = FinalMarking;
  } // Else, a foreground collection completed this CMS cycle.
  return;
}
  1. 可以設(shè)置最多循環(huán)的次數(shù) CMSMaxAbortablePrecleanLoops,默認(rèn)是0,意思沒有循環(huán)次數(shù)的限制。

  2. 如果執(zhí)行這個(gè)邏輯的時(shí)間達(dá)到了閾值CMSMaxAbortablePrecleanTime,默認(rèn)是5s,會(huì)退出循環(huán)(等待一次YGC)。

2020-07-02T09:37:53.753+0800: 238204.309: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 2020-07-02T09:37:58.785+0800: 238209.342: [CMS-concurrent-abortable-preclean: 3.128/5.032 secs] [Times: user=4.55 sys=0.91, real=5.03 secs] 

參考:https://www.cnblogs.com/zhangxiaoguang/p/5792468.html

3.128/5.032 secs 展示該階段持續(xù)的時(shí)間和時(shí)鐘時(shí)間(It is interesting to note that the user time reported is a lot smaller than clock time. Usually we have seen that real time is less than user time, meaning that some work was done in parallel and so elapsed clock time is less than used CPU time. Here we have a little amount of work – for 0.167 seconds of CPU time, and garbage collector threads were doing a lot of waiting. Essentially, they were trying to stave off for as long as possible before having to do an STW pause. By default, this phase may last for up to 5 seconds)

  1. 如果新生代Eden區(qū)的內(nèi)存使用率達(dá)到了閾值CMSScheduleRemarkEdenPenetration,默認(rèn)50%,會(huì)退出循環(huán)。(這個(gè)條件能夠成立的前提是,在進(jìn)行Precleaning時(shí),Eden區(qū)的使用率小于10%
if ((_collectorState == AbortablePreclean) && !_abort_preclean) {
  size_t used = get_eden_used();
  size_t capacity = get_eden_capacity();
  assert(used <= capacity, "Unexpected state of Eden");
  if (used >  (capacity/100 * CMSScheduleRemarkEdenPenetration)) {
    _abort_preclean = true;
  }
}

問題:如果超過50%退出,則永遠(yuǎn)不可能產(chǎn)品YGC,如果不退出,要等5秒浪費(fèi)時(shí)間?

  1. 新生代小
  2. 50%避免REMARK 階段產(chǎn)生YGC

如果在AbortablePreclean階段沒來得及執(zhí)行一次YGC,怎么辦?

  • CMS算法中提供了一個(gè)參數(shù):CMSScavengeBeforeRemark,默認(rèn)并沒有開啟,如果開啟該參數(shù),在執(zhí)行該階段之前,會(huì)強(qiáng)制觸發(fā)一次YGC,可以減少新生代對象的遍歷時(shí)間,回收的也更徹底一點(diǎn)。
    不過,這種參數(shù)有利有弊,利是降低了Remark階段的停頓時(shí)間,弊的是在新生代對象很少的情況下也多了一次YGC,最可憐的是在AbortablePreclean階段已經(jīng)發(fā)生了一次YGC,然后在該階段又傻傻的觸發(fā)一次。所以利弊需要把握。一般CMS的GC耗時(shí) 80%都在remark階段,如果發(fā)現(xiàn)remark階段停頓時(shí)間很長,可以嘗試添加該參數(shù)

重新標(biāo)記(STW remark)

預(yù)清理階段也是并發(fā)執(zhí)行的,并不一定是所有存活對象都會(huì)被標(biāo)記,因?yàn)樵诓l(fā)標(biāo)記的過程中對象及其引用關(guān)系還在不斷變化中。

因此, 需要有一個(gè)stop-the-world的階段來完成最后的標(biāo)記工作 ,這就是重新標(biāo)記階段(CMS標(biāo)記階段的最后一個(gè)階段)。 主要目的是重新掃描之前并發(fā)處理階段的所有殘留更新對象 。

image

可以開啟并行收集:-XX:+CMSParallelRemarkEnabled

重新標(biāo)記的內(nèi)存范圍是整個(gè)堆,包含younggen和oldgen 如果被新生代中的對象引用,那么就會(huì)被視為存活對象,即使新生代的對象已經(jīng)不可達(dá)了,也會(huì)使用這些不可達(dá)的對象當(dāng)做cms的“gc root”,來掃描老年代

主要工作:

  • 遍歷新生代對象,重新標(biāo)記;(新生代會(huì)被分塊,多線程掃描)

  • 根據(jù)GC Roots,重新標(biāo)記;

  • 遍歷老年代的Dirty Card,重新標(biāo)記。這里的Dirty Card,大部分已經(jīng)在Preclean階段被處理過了。

[GC (CMS Final Remark) [YG occupancy: 1120265 K (2146944 K)]2019-08-30T17:59:30.896+0800: 9938.409:
[Rescan (parallel) , 0.0345201 secs]2019-08-30T17:59:30.930+0800: 9938.444:
[weak refs processing, 1.3559616 secs]2019-08-30T17:59:32.286+0800: 9939.800:
[class unloading, 0.0689365 secs]2019-08-30T17:59:32.355+0800: 9939.869: [scrub symbol table, 0.0166362 secs]2019-08-30T17:59:32.372+0800: 9939.885:
[scrub string table, 0.0013061 secs][1 CMS-remark: 21874968K(29071808K)] 22995234K(31218752K), 1.9651057 secs] [Times: user=2.80 sys=0.07, real=1.96 secs]

并發(fā)清理(Concurrent sweeping)

并發(fā)清理階段,主要工作是 清理所有未被標(biāo)記的死亡對象,回收被占用的空間 。

image

并發(fā)重置(Concurrent reset)

并發(fā)重置階段,將清理并恢復(fù)在CMS GC過程中的各種狀態(tài),重新初始化CMS相關(guān)數(shù)據(jù)結(jié)構(gòu) ,為下一個(gè)垃圾收集周期做好準(zhǔn)備。

image

CMS缺點(diǎn)

  • CMS回收器采用的基礎(chǔ)算法是Mark-Sweep。所有CMS不會(huì)整理、壓縮堆空間。這樣就會(huì)有一個(gè)問題:經(jīng)過CMS收集的堆會(huì)產(chǎn)生空間碎片。 跟碎片相關(guān)的幾個(gè)參數(shù):

    1. -XX:+UseCMSCompactAtFullCollection:FULL GC后進(jìn)行內(nèi)存壓縮,該參數(shù)與-XX:CMSFullGCsBeforeCompaction配合使用,該參數(shù)默認(rèn)為true。-XX:CMSFullGCsBeforeCompaction:FULL GC多少次后進(jìn)行內(nèi)存壓縮,CMS回退到full GC時(shí)用的算法是mark-sweep-compact(標(biāo)記-清除-壓縮 也叫 標(biāo)記-整理),但compaction是可選的,不做的話碎片化會(huì)嚴(yán)重些但這次full GC的暫停時(shí)間會(huì)短些,這是個(gè)取舍。`

    2. -XX:+CMSFullGCsBeforeCompaction 說的是,在上一次CMS并發(fā)GC執(zhí)行過后,到底還要再執(zhí)行多少次full GC才會(huì)做壓縮。默認(rèn)是0,也就是在默認(rèn)配置下每次CMS GC頂不住了而要轉(zhuǎn)入full GC的時(shí)候都會(huì)做壓縮。 把CMSFullGCsBeforeCompaction配置為10,就會(huì)讓上面說的第一個(gè)條件變成每隔10次真正的full GC才做一次壓縮(而不是每10次CMS并發(fā)GC就做一次壓縮,目前JVM里沒有這樣的參數(shù))。這會(huì)使full GC更少做壓縮,也就更容易使CMS的old gen受碎片化問題的困擾。 本來這個(gè)參數(shù)就是用來配置降低full GC壓縮的頻率,以期減少某些full GC的暫停時(shí)間。CMS回退到full GC時(shí)用的算法是mark-sweep-compact,但compaction是可選的,不做的話碎片化會(huì)嚴(yán)重些但這次full GC的暫停時(shí)間會(huì)短些;這是個(gè)取舍。`

盡量不要出現(xiàn) CMS的FULL GC,如果出現(xiàn)FULL GC頻繁,優(yōu)化不上去的話,可以考慮G1

  • 需要更多的CPU資源。從上面的圖可以看到,為了讓應(yīng)用程序不停頓,CMS線程和應(yīng)用程序線程并發(fā)執(zhí)行,這樣就需要有更多的CPU,單純靠線程切 換是不靠譜的。并且,重新標(biāo)記階段,為空保證STW快速完成,也要用到更多的甚至所有的CPU資源。時(shí)間與吞吐需要平衡

  • CMS的另一個(gè)缺點(diǎn)是它需要更大的堆空間。因?yàn)镃MS標(biāo)記階段應(yīng)用程序的線程還是在執(zhí)行的,那么就會(huì)有堆空間繼續(xù)分配的情況,為了保證在CMS回收完堆之前還有空間分配給正在運(yùn)行的應(yīng)用程序,必須預(yù)留一部分空間。也就是說,CMS不會(huì)在老年代滿的時(shí)候才開始收集。相反,它會(huì)嘗試更早的開始收集。 – XX:CMSInitiatingOccupancyFraction =n 來設(shè)置這個(gè)閥值。

void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) {

 assert(io <= 100 && tr <= 100, "Check the arguments");

 if (io >= 0) {

   _initiating_occupancy = (double)io / 100.0;

} else {

    _initiating_occupancy = ((100 - MinHeapFreeRatio) +

(double)(tr * MinHeapFreeRatio) / 100.0)/ 100.0;

  }

}

其中兩個(gè)參數(shù)

  1. io就是CMSInitiatingOccupancyFraction的值,如果你沒設(shè)置過就是虛擬機(jī)自己的默認(rèn)值,默認(rèn)-1,可以用指令來查看:
java -XX:+PrintFlagsFinal -version |grep CMSInitiatingOccupancyFraction

     intx CMSInitiatingOccupancyFraction            = -1 {product}

java version "1.8.0_221"

Java(TM) SE Runtime Environment (build 1.8.0_221-b11)

Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
  1. tr就是JVM啟動(dòng)參數(shù)CMSTriggerRatio的值,沒手動(dòng)設(shè)置過的話也同樣也可以用指令來查看默認(rèn)值:
 java -XX:+PrintFlagsFinal -version |grep CMSTriggerRatio
uintx CMSTriggerRatio                           = 80 {product}
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
  1. MinHeapFreeRatio
java -XX:+PrintFlagsFinal -version |grep MinHeapFreeRatio
uintx MinHeapFreeRatio                          = 0  {manageable}
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

總結(jié):

  1. CMSInitiatingOccupancyFraction默認(rèn)值是-1,并不是許多人說的68、92之類的。

  2. CMSInitiatingOccupancyFraction是用來計(jì)算老年代最大使用率(_initiating_occupancy)的。大于等于0則直接取百分號(hào),小于0則根據(jù)公式來計(jì)算。這個(gè)_initiating_occupancy需要配合-XX:+UseCMSInitiatingOccupancyOnly來使用。

  3. 不同版本最終得到的_initiating_occupancy不同,歸根結(jié)底應(yīng)該是不同版本下的MinHeapFreeRatio值不同。

總得來說,CMS回收器減少了回收的停頓時(shí)間,但是降低了堆空間的利用率。

關(guān)鍵參數(shù)說明

  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOG_DIR/heap_dump_%p.log

  • -XX:MaxTenuringThreshold=15 年齡

  • -XX:+UseParNewGC

  • -XX:+UseConcMarkSweepGC

  • -XX:CMSInitiatingOccupancyFraction=70 和-XX:+UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction=70 是指設(shè)定CMS在對內(nèi)存占用率達(dá)到70%的時(shí)候開始GC(因?yàn)镃MS會(huì)有浮動(dòng)垃圾,所以一般都較早啟動(dòng)GC);

  • -XX:+UseCMSInitiatingOccupancyOnly 只是用設(shè)定的回收閾值(上面指定的70%),如果不指定,JVM僅在第一次使用設(shè)定值,后續(xù)則自動(dòng)調(diào)整.

  • -XX:+CMSScavengeBeforeRemark 在CMS GC前啟動(dòng)一次ygc,目的在于減少ygc gen對的old gen引用,降低remark時(shí)的開銷-----一般CMS的GC耗時(shí) 80%都在remark階段。

G1

三色標(biāo)記算法

image

RSET 代際引用

Rset 是一種抽象概念,記錄對象在不同代際之間的引用關(guān)系,目的是為了加速垃圾回收的速度。JVM使用根對象引用的收集算法,即從根命令出發(fā),標(biāo)記所有存活的對象,然后遍歷對象的每一個(gè)了段繼續(xù)標(biāo)記,直到所有的對象標(biāo)記完成。在分代GC中,我們知道新生代和老年代處于不同的收集階段,如果還是按照這種標(biāo)記方法,既不合理也沒必要。假設(shè)我們只收集年輕代,我們要把老年代全部標(biāo)記,但是并沒有收集老年代,浪費(fèi)了時(shí)間。同理在收集老年代時(shí)有同樣的問題。當(dāng)且僅當(dāng)我們要進(jìn)行Full GC時(shí),才需要做全部標(biāo)記。所以算法設(shè)計(jì)者做了這樣的設(shè)計(jì),用一個(gè)Rset 記錄非收集部分指向收集部分的指針集合,而這個(gè)集合描述對象的引用關(guān)系。

根據(jù)這個(gè)思路,我們總結(jié)分區(qū)之間的引用關(guān)系。

  • 分區(qū)內(nèi)部的引用關(guān)系

無論是新生代分區(qū)還是老年代內(nèi)部分區(qū)的引用關(guān)系都無需記錄,因?yàn)榛厥帐轻槍φ麄€(gè)分區(qū)而言,要么被回收,要么不被回收,回收的時(shí)侯會(huì)遍歷整個(gè)分區(qū),所以無需記錄這種額外的引用關(guān)系。

  • 新生代分區(qū)到新生代分區(qū)之間的引用關(guān)系

這個(gè)也無需記錄,原因在于G1的回收算法都會(huì)全量處理新生代分區(qū),所以它們會(huì)被遍歷。

  • 新生代分區(qū)到老年代分區(qū)之間的引用關(guān)系

這個(gè)無需記錄,G1會(huì)用新生代分區(qū)作為根,所以在遍歷新生代時(shí)自然會(huì)找到老年代分區(qū),所以無需記錄。

  • 老年代分區(qū)到新生代分區(qū)之間的引用關(guān)系

這個(gè)需要記錄,在YGC的時(shí)侯有兩種根,一個(gè)就是??臻g、全局變量的引用,另一個(gè)就是老年代分區(qū)到新生代分區(qū)的引用。

  • 老年代分到老年代分區(qū)之間的引用

這個(gè)需要記錄,在混合GC的時(shí)侯可能只有部分分區(qū)被回收,所以必須記錄引用關(guān)系,快速找到哪些對象是活躍的。

G1 STAB 解決新創(chuàng)建對象產(chǎn)生的漏標(biāo)問題

image

官方參數(shù)說明

英文原文https://www.oracle.com/technical-resources/articles/java/g1gc.html

中文文檔https://www.oracle.com/cn/technical-resources/articles/java/g1gc.html https://www.cnblogs.com/cuizhiquan/articles/11058497.html

G1 回收過程如下。

  1. G1 的年輕代回收,采用復(fù)制算法,并行進(jìn)行收集,收集過程會(huì) STW。
  2. G1 的老年代回收時(shí)也同時(shí)會(huì)對年輕代進(jìn)行回收。主要分為四個(gè)階段:
    2.1 初始標(biāo)記階段完成對根對象的標(biāo)記,這個(gè)過程是STW的;
    2.2. 并發(fā)標(biāo)記階段,這個(gè)階段是和用戶線程并發(fā)執(zhí)行的;
    2.3 最終標(biāo)記階段,完成三色標(biāo)記周期(STW)
    2.4 復(fù)制/清除階段,這個(gè)階段會(huì)優(yōu)先對可回收空間較大的 Region 進(jìn)行回收,即 garbage first,這也是 G1 名稱的由來。(STW)

G1采用每次只清理一部分而不是全部的 Region 的增量式清理,由此來保證每次GC停頓時(shí)間不會(huì)過長。

總結(jié)如下,G1 是邏輯分代不是物理劃分,需要知道回收的過程和停頓的階段。此外還需要知道,G1 算法允許通過 JVM 參數(shù)設(shè)置 Region 的大小,范圍是 1~32MB,可以設(shè)置期望的最大 GC 停頓時(shí)間等。

image
image

https://segmentfault.com/a/1190000022537037

參數(shù)配置

參數(shù)及默認(rèn)值 說明
-XX:G1HeapRegionSize = n 設(shè)置region的大小,該值為2的冪,范圍為1MB到32 MB,目標(biāo)是根據(jù)最小Java堆大小,將堆分成大約2048個(gè)region。
-XX:MaxGCPauseMillis = 200 設(shè)置最大停頓時(shí)間,默認(rèn)值為200毫秒。
-XX:G1NewSizePercent = 5 設(shè)置年輕代占整個(gè)堆的最小百分比,默認(rèn)值是5,這是個(gè)實(shí)驗(yàn)參數(shù),如果設(shè)置該值,將覆蓋默認(rèn)參數(shù) -XX:DefaultMinNewGenPercent。(JVM build > 23)
-XX:G1MaxNewSizePercent = 60 設(shè)置年輕代占整個(gè)堆的最大百分比,默認(rèn)值是60,這是個(gè)實(shí)驗(yàn)參數(shù),如果設(shè)置該值,將覆蓋默認(rèn)參數(shù) -XX:DefaultMaxNewGenPercent。(JVM build > 23)
-XX:ParallelGCThreads = n 設(shè)置STW的垃圾收集線程數(shù),當(dāng)邏輯處理器數(shù)量小于8時(shí),n的值與邏輯處理器數(shù)量相同;如果邏輯處理器數(shù)量大于8個(gè),則n的值大約為邏輯處理器數(shù)量的5/8,大多數(shù)情況下是這樣,除了較大的SPARC系統(tǒng),其中n的值約為邏輯處理器的5/16。
-XX:ConcGCThreads = n 設(shè)置并行標(biāo)記線程的數(shù)量,設(shè)置n大約為ParallelGCThreads參數(shù)值的1/4。
-XX:InitiatingHeapOccupancyPercent = 45 設(shè)置觸發(fā)標(biāo)記周期的Java堆占用閾值,默認(rèn)值為45。
-XX:G1MixedGCLiveThresholdPercent = 65 這個(gè)參數(shù)表示如果一個(gè)分區(qū)中的存活對象比例超過n,就不會(huì)被挑選為垃圾分區(qū)(JVM build > 23)
-XX:G1HeapWastePercent=10 設(shè)置浪費(fèi)的堆內(nèi)存百分比,當(dāng)可回收百分比小于浪費(fèi)百分比時(shí),JVM就不會(huì)啟動(dòng)混合垃圾收。(就是設(shè)置垃圾對象占用內(nèi)存百分比的最大值)。(JVM build > 23)
-XX:G1MixedGCCountTarget = 8 設(shè)置在標(biāo)記周期完成之后混合收集的數(shù)量,以維持old region(也就是老年代)中,最多有G1MixedGCLiveThresholdPercent的存活對象。默認(rèn)值為8,混合收集的數(shù)量將維持在這個(gè)值之內(nèi)。(JVM build > 23)
-XX:G1OldCSetRegionThresholdPercent = 10 設(shè)置在一次混合收集中被收集的old region數(shù)量的上線,默認(rèn)值是整個(gè)堆的10%。(JVM build > 23)
-XX:G1ReservePercent = 10 設(shè)置預(yù)留空閑內(nèi)存百分比,以降低內(nèi)存溢出的風(fēng)險(xiǎn)。默認(rèn)值為10%。增加或減少百分比時(shí),請確保將總Java堆調(diào)整相同的量。(JVM build > 23)

注:表格中的 JVM build 23表示該參數(shù)只有在Java HotSpot VM build 大于 23時(shí)才能生效,小于或等于都不能生效

-XX:+InitiatingHeapOccupancyPercent(簡稱IHOP):G1內(nèi)部并行循環(huán)啟動(dòng)的設(shè)置值,默認(rèn)為Java Heap的45%。這個(gè)可以理解為老年代空間占用的空間,GC收集后需要低于45%的占用率。這個(gè)值主要是為了決定在什么時(shí)間啟動(dòng)老年代的并行回收循環(huán),這個(gè)循環(huán)從初始化并行回收開始,可以避免Full GC的發(fā)生;

-XX:G1HeapWastePercent:G1不會(huì)回收的內(nèi)存大小,默認(rèn)是堆大小的10%。意思是允許整個(gè)堆內(nèi)存中有10%的空間被浪費(fèi),意味著如果發(fā)現(xiàn)可以回收的垃圾占堆內(nèi)存的比例低于10%,則不再進(jìn)行混合回收。因?yàn)镚C會(huì)花費(fèi)很多的時(shí)間但是回收到的內(nèi)存卻很少。

-XX:G1MixedGCLiveThresholdPercent,默認(rèn)為65%,意思是垃圾占內(nèi)存 分段比例要達(dá)到65%才會(huì)被回收。如果垃圾占比太低,意味著存活的對象占比高,在復(fù)制的時(shí)候會(huì)花費(fèi)更多的時(shí)間。

G1的回收策略

  1. 新生代老年代是個(gè)邏輯的概念,即同一個(gè)Region在系統(tǒng)經(jīng)歷過若干次垃圾回收后,做過新也當(dāng)過老,也有可能新老都沒有作過
  2. 空間分配的單位是Region
  3. 雖然新老是個(gè)邏輯概念,但他們是有自己的大小上下限的。默認(rèn)情況下新最大占60%,老年代最大占(1 - 5%的新生代初始值)=95%
  4. 觸發(fā)和回收新生代和之前的過程一樣。不同點(diǎn)在于:由于MaxGCPauseMills的設(shè)定,只會(huì)在指定時(shí)間內(nèi)回收掉盡可能多的垃圾對象
  5. 進(jìn)入老年代的條件:
    1. 年齡大了
    2. 動(dòng)態(tài)年齡判斷
    3. 存活對象Survivor放不下
    4. 大于單個(gè)Region 50%的屬于大對象

GC模式

G1中提供了三種模式垃圾回收模式,young gc、mixed gc 和 full gc,在不同的條件下被觸發(fā)。

  • young gc

發(fā)生在年輕代的GC算法,一般對象(除了巨型對象)都是在eden region中分配內(nèi)存,當(dāng)所有eden region被耗盡無法申請內(nèi)存時(shí),就會(huì)觸發(fā)一次young gc,這種觸發(fā)機(jī)制和之前的young gc差不多,執(zhí)行完一次young gc,活躍對象會(huì)被拷貝到survivor region或者晉升到old region中,空閑的region會(huì)被放入空閑列表中,等待下次被使用。

  • mixed gc
    當(dāng)越來越多的對象晉升到老年代old region時(shí),為了避免堆內(nèi)存被耗盡,虛擬機(jī)會(huì)觸發(fā)一個(gè)混合的垃圾收集器,即mixed gc,該算法并不是一個(gè)old gc,除了回收整個(gè)young region,還會(huì)回收一部分的old region,這里需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進(jìn)行收集,從而可以對垃圾回收的耗時(shí)時(shí)間進(jìn)行控制。
  • 那么mixed gc什么時(shí)候被觸發(fā)?
    先回顧一下cms的觸發(fā)機(jī)制,如果添加了以下參數(shù)
    -XX:CMSInitiatingOccupancyFraction=75
    -XX:+UseCMSInitiatingOccupancyOnly 當(dāng)老年代的使用率達(dá)到75%時(shí),就會(huì)觸發(fā)一次cms gc。相對的,mixed gc中也有一個(gè)閾值參數(shù)-XX:InitiatingHeapOccupancyPercent,當(dāng)老年代大小整個(gè)堆大小百分比達(dá)到該閾值時(shí),會(huì)觸發(fā)一次mixed gc.
  • full gc 如果對象內(nèi)存分配速度過快,mixed gc來不及回收,導(dǎo)致老年代被填滿,就會(huì)觸發(fā)一次full gc,G1的full gc算法就是單線程執(zhí)行的serial old gc,會(huì)導(dǎo)致異常長時(shí)間的暫停時(shí)間,需要進(jìn)行不斷的調(diào)優(yōu),盡可能的避免full gc.

UnlockExperimentalVMOptions

Error: VM option 'G1NewSizePercent' is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.

Error: Could not create the Java Virtual Machine.

Error: A fatal exception has occurred. Program will exit.

XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=70

啥時(shí)候用G1

  • 優(yōu)先原則是從業(yè)務(wù)先優(yōu)化,如果業(yè)務(wù)的數(shù)據(jù)(存儲(chǔ))和算法無法優(yōu)化的情況下,可以考慮JVM 調(diào)優(yōu)

  • 有實(shí)驗(yàn)表明G1在回收Eden分區(qū)的時(shí)候,大概每GB需要100ms,如果GC回收時(shí)間超過500ms以上,可以考慮用G1!!

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc.html#garbage_first_garbage_collection

image.png

參考資料

https://crowhawk.github.io/2017/08/15/jvm_3/

https://stackoverflow.com/questions/39569649/why-is-cms-full-gc-single-threaded?from=timeline&isappinstalled=0

https://blogs.oracle.com/jonthecollector/our-collectors

https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc?from=timeline&isappinstalled=0

https://juejin.im/post/5ed32ec96fb9a0480659e547

https://developer.aliyun.com/article/724633

https://segmentfault.com/a/1190000022537037

https://plumbr.io/handbook/garbage-collection-algorithms-implementations/concurrent-mark-and-sweep

http://ifeve.com/jvm-cms-log/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容