可能是最全面的G1學習筆記

来源:https://www.cnblogs.com/javaadu/archive/2019/04/16/10713956.html
-Advertisement-
Play Games

引子 最近遇到很多朋友過來咨詢G1調優的問題,我自己去年有專門學過一次G1,但是當時只是看了個皮毛,因此自己也有不少問題。總體來講,對於G1我有幾個疑惑,希望能夠在這篇文章中得到解決。 1. G1出現的初衷是什麼? 2. G1適合在什麼場景下使用? 3. G1的trade off是什麼? 4. G1 ...


引子

最近遇到很多朋友過來咨詢G1調優的問題,我自己去年有專門學過一次G1,但是當時只是看了個皮毛,因此自己也有不少問題。總體來講,對於G1我有幾個疑惑,希望能夠在這篇文章中得到解決。

  1. G1出現的初衷是什麼?
  2. G1適合在什麼場景下使用?
  3. G1的trade-off是什麼?
  4. G1的詳細過程?
  5. 如何理解G1的gc日誌?
  6. G1的調優思路?
  7. G1和CMS的對比和選擇?

一、基礎知識

1. 初衷

在G1提出之前,經典的垃圾收集器主要有三種類型:串列收集器、並行收集器和併發標記清除收集器,這三種收集器分別可以是滿足Java應用三種不同的需求:記憶體占用及併發開銷最小化、應用吞吐量最大化和應用GC暫停時間最小化,但是,上述三種垃圾收集器都有幾個共同的問題:(1)所有針對老年代的操作必須掃描整個老年代空間;(2)新生代和老年代是獨立的連續的記憶體塊,必須先決定年輕代和老年代在虛擬地址空間的位置。

2. 設計目標

G1是一種服務端應用使用的垃圾收集器,目標是用在多核、大記憶體的機器上,它在大多數情況下可以實現指定的GC暫停時間,同時還能保持較高的吞吐量。

3. 使用場景

G1適用於以下幾種應用:

  • 可以像CMS收集器一樣,允許垃圾收集線程和應用線程並行執行,即需要額外的CPU資源;
  • 壓縮空閑空間不會延長GC的暫停時間;
  • 需要更易預測的GC暫停時間;
  • 不需要實現很高的吞吐量

二、G1的重要概念

1. 分區(Region)

G1採取了不同的策略來解決並行、串列和CMS收集器的碎片、暫停時間不可控制等問題——G1將整個堆分成相同大小的分區(Region),如下圖所示。G1的堆模型

每個分區都可能是年輕代也可能是老年代,但是在同一時刻只能屬於某個代。
年輕代、幸存區、老年代這些概念還存在,成為邏輯上的概念,這樣方便復用之前分代框架的邏輯。在物理上不需要連續,則帶來了額外的好處——有的分區內垃圾對象特別多,有的分區內垃圾對象很少,G1會優先回收垃圾對象特別多的分區,這樣可以花費較少的時間來回收這些分區的垃圾,這也就是G1名字的由來,即首先收集垃圾最多的分區。

新生代其實並不是適用於這種演算法的,依然是在新生代滿了的時候,對整個新生代進行回收——整個新生代中的對象,要麼被回收、要麼晉升,至於新生代也採取分區機制的原因,則是因為這樣跟老年代的策略統一,方便調整代的大小。

G1還是一種帶壓縮的收集器,在回收老年代的分區時,是將存活的對象從一個分區拷貝到另一個可用分區,這個拷貝的過程就實現了局部的壓縮。每個分區的大小從1M到32M不等,但是都是2的冥次方。

2. 收集集合(CSet)

一組可被回收的分區的集合。在CSet中存活的數據會在GC過程中被移動到另一個可用分區,CSet中的分區可以來自Eden空間、survivor空間、或者老年代。CSet會占用不到整個堆空間的1%大小。

3. 已記憶集合(RSet)

RSet記錄了其他Region中的對象引用本Region中對象的關係,屬於points-into結構(誰引用了我的對象)。RSet的價值在於使得垃圾收集器不需要掃描整個堆找到誰引用了當前分區中的對象,只需要掃描RSet即可。

如下圖所示,Region1和Region3中的對象都引用了Region2中的對象,因此在Region2的RSet中記錄了這兩個引用。
RSet的示意圖

摘一段R大的解釋:G1 GC則是在points-out的card table之上再加了一層結構來構成points-into RSet:每個region會記錄下到底哪些別的region有指向自己的指針,而這些指針分別在哪些card的範圍內。 這個RSet其實是一個hash table,key是別的region的起始地址,value是一個集合,裡面的元素是card table的index。 舉例來說,如果region A的RSet里有一項的key是region B,value里有index為1234的card,它的意思就是region B的一個card里有引用指向region A。所以對region A來說,該RSet記錄的是points-into的關係;而card table仍然記錄了points-out的關係。

4. Snapshot-At-The-Beginning(SATB)

SATB是維持併發GC的正確性的一個手段,G1GC的併發理論基礎就是SATB,SATB是由Taiichi Yuasa為增量式標記清除垃圾收集器設計的一個標記演算法。Yuasa的SATAB的標記優化主要針對標記-清除垃圾收集器的併發標記階段。按照R大的說法:CMS的incremental update設計使得它在remark階段必須重新掃描所有線程棧和整個young gen作為root;G1的SATB設計在remark階段則只需要掃描剩下的satb_mark_queue。

SATB演算法創建了一個對象圖,它是堆的一個邏輯“快照”。標記數據結構包括了兩個點陣圖:previous點陣圖和next點陣圖。previous點陣圖保存了最近一次完成的標記信息,併發標記周期會創建並更新next點陣圖,隨著時間的推移,previous點陣圖會越來越過時,最終在併發標記周期結束的時候,next點陣圖會將previous點陣圖覆蓋掉。
下麵我們以幾個圖例來描述SATB演算法的過程:

  1. 在併發周期開始之前,NTAMS欄位被設置到每個分區當前的頂部,併發周期啟動後分配的對象會被放在TAMS之前(圖裡下邊的部分),同時被明確定義為隱式存活對象,而TAMS之後(圖裡上邊的部分)的對象則需要被明確地標記。
    初始標記過程中的一個堆分區

  2. 併發標記過程中的堆分區
    併發標記過程中的對分區

  3. 位於堆分區的Bottom和PTAMS之間的對象都會被標記並記錄在previous點陣圖中;
    位於Bottom和PTAMS之間的對象都會被標記在previous點陣圖中

  4. 位於堆分區的Top和PATMS之間的對象均為隱式存活對象,同時也記錄在previous點陣圖中;
    隱式存活標記,是一種增量標記

  5. 在重新標記階段的最後,所有NTAMS之前的對象都會被標記
    重新標記

  6. 在併發標記階段分配的對象會被分配到NTAMS之後的空間,它們會作為隱式存活對象被記錄在next點陣圖中。一次併發標記周期完成後,這個next點陣圖會覆蓋previous點陣圖,然後將next點陣圖清空。
    開始併發標記後的對象會被識別為隱式存活對象,放在next點陣圖中

SATB是一個快照標記演算法,在併發標記進行的過程中,垃圾收集器(Collecotr)和應用程式(Mutator)都在活動,如果一個對象還沒被mark到,這時候Mutator就修改了它的引用,那麼這時候拿到的快照就是不完整的了,如何解決這個問題呢?G1 GC使用了SATB write barrier來解決這個問題——在併發標記過程中,將該對象的舊的引用記錄在一個SATB日誌對列或緩衝區中。去翻G1的代碼,卻發現實際代碼如下——只該對象入隊列,並沒有將整個修改過程放在寫屏障之間完成。

  // hotspot/src/share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.hpp
  // This notes that we don't need to access any BarrierSet data
  // structures, so this can be called from a static context.
  template <class T> static void write_ref_field_pre_static(T* field, oop newVal) {
    T heap_oop = oopDesc::load_heap_oop(field);
    if (!oopDesc::is_null(heap_oop)) {
      enqueue(oopDesc::decode_heap_oop(heap_oop));
    }
  }

enqueue的真正代碼在hotspot/src/share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.cpp中,這裡使用JavaThread::satb_mark_queue_set().is_active()判斷是否處於併發標記周期。

void G1SATBCardTableModRefBS::enqueue(oop pre_val) {
  // Nulls should have been already filtered.
  assert(pre_val->is_oop(true), "Error");

  if (!JavaThread::satb_mark_queue_set().is_active()) return;
  Thread* thr = Thread::current();
  if (thr->is_Java_thread()) {
    JavaThread* jt = (JavaThread*)thr;
    //將舊值入隊
    jt->satb_mark_queue().enqueue(pre_val);
  } else {
    MutexLockerEx x(Shared_SATB_Q_lock, Mutex::_no_safepoint_check_flag);
    JavaThread::satb_mark_queue_set().shared_satb_queue()->enqueue(pre_val);
  }
}

stab_mark_queue.enqueue方法首先嘗試將以前的值記錄在一個緩衝區中,如果這個緩衝區已經滿了,就會將當期這個SATB緩衝區“退休”並放入全局列表中,然後再給線程分配一個新的SATB緩衝區。併發標記線程會定期檢查和處理那些“被填滿”的緩衝區。

三、G1的過程

1. 四個操作

G1收集器的收集活動主要有四種操作:

  • 新生代垃圾收集
  • 後臺收集、併發周期
  • 混合式垃圾收集
  • 必要時候的Full GC

第一、新生代垃圾收集的圖例如下:image.png

  • Eden區耗盡的時候就會觸發新生代收集,新生代垃圾收集會對整個新生代進行回收
  • 新生代垃圾收集期間,整個應用STW
  • 新生代垃圾收集是由多線程併發執行的
  • 新生代收集結束後依然存活的對象,會被拷貝到一個新的Survivor分區,或者是老年代。

G1設計了一個標記閾值,它描述的是總體Java堆大小的百分比,預設值是45,這個值可以通過命令-XX:InitiatingHeapOccupancyPercent(IHOP)來調整,一旦達到這個閾值就回觸發一次併發收集周期。註意:這裡的百分比是針對整個堆大小的百分比,而CMS中的CMSInitiatingOccupancyFraction命令選型是針對老年代的百分比。併發收集周期的圖例如下:image.png

在上圖中有幾個情況需要註意:

  • 新生代的空間占用情況發生了變化——在併發收集周期中,至少有一次(很可能是多次)新生代垃圾收集;
  • 註意到一些分區被標記為X,這些分區屬於老年代,它們就是標記周期找出的包含最多垃圾的分區(註意:它們內部仍然保留著數據);
  • 老年代的空間占用在標記周期結束後變得更多,這是因為在標記周期期間,新生代的垃圾收集會晉升對象到老年代,而且標記周期中並不會是否老年代的任何對象。

第二、G1的併發標記周期包括多個階段:
併發標記周期採用的演算法是我們前文提到的SATB標記演算法,產出是找出一些垃圾對象最多的老年代分區。

  • 初始標記(initial-mark),在這個階段,應用會經歷STW,通常初始標記階段會跟一次新生代收集一起進行,換句話說——既然這兩個階段都需要暫停應用,G1 GC就重用了新生代收集來完成初始標記的工作。在新生代垃圾收集中進行初始標記的工作,會讓停頓時間稍微長一點,並且會增加CPU的開銷。初始標記做的工作是設置兩個TAMS變數(NTAMS和PTAMS)的值,所有在TAMS之上的對象在這個併發周期內會被識別為隱式存活對象;
  • 根分區掃描(root-region-scan),這個過程不需要暫停應用,在初始標記或新生代收集中被拷貝到survivor分區的對象,都需要被看做是根,這個階段G1開始掃描survivor分區,所有被survivor分區所引用的對象都會被掃描到並將被標記。survivor分區就是根分區,正因為這個,該階段不能發生新生代收集,如果掃描根分區時,新生代的空間恰好用盡,新生代垃圾收集必須等待根分區掃描結束才能完成。如果在日誌中發現根分區掃描和新生代收集的日誌交替出現,就說明當前應用需要調優。
  • 併發標記階段(concurrent-mark),併發標記階段是多線程的,我們可以通過-XX:ConcGCThreads來設置併發線程數,預設情況下,G1垃圾收集器會將這個線程總數設置為並行垃圾線程數(-XX:ParallelGCThreads)的四分之一;併發標記會利用trace演算法找到所有活著的對象,並記錄在一個bitmap中,因為在TAMS之上的對象都被視為隱式存活,因此我們只需要遍歷那些在TAMS之下的;記錄在標記的時候發生的引用改變,SATB的思路是在開始的時候設置一個快照,然後假定這個快照不改變,根據這個快照去進行trace,這時候如果某個對象的引用發生變化,就需要通過pre-write barrier logs將該對象的舊的值記錄在一個SATB緩衝區中,如果這個緩衝區滿了,就把它加到一個全局的列表中——G1會有併發標記的線程定期去處理這個全局列表。
  • 重新標記階段(remarking),重新標記階段是最後一個標記階段,需要暫停整個應用,G1垃圾收集器會處理掉剩下的SATB日誌緩衝區和所有更新的引用,同時G1垃圾收集器還會找出所有未被標記的存活對象。這個階段還會負責引用處理等工作。
  • 清理階段(cleanup),清理階段真正回收的記憶體很小,截止到這個階段,G1垃圾收集器主要是標記處哪些老年代分區可以回收,將老年代按照它們的存活度(liveness)從小到大排列。這個過程還會做幾個事情:識別出所有空閑的分區、RSet梳理、將不用的類從metaspace中卸載、回收巨型對象等等。識別出每個分區里存活的對象有個好處是在遇到一個完全空閑的分區時,它的RSet可以立即被清理,同時這個分區可以立刻被回收並釋放到空閑隊列中,而不需要再放入CSet等待混合收集階段回收;梳理RSet有助於發現無用的引用。

第三、混合收集只會回收一部分老年代分區,下圖是第一次混合收集前後的堆情況對比。image.png

混合收集會執行多次,一直運行到(幾乎)所有標記點老年代分區都被回收,在這之後就會恢復到常規的新生代垃圾收集周期。當整個堆的使用率超過指定的百分比時,G1 GC會啟動新一輪的併發標記周期。在混合收集周期中,對於要回收的分區,會將該分區中存活的數據拷貝到另一個分區,這也是為什麼G1收集器最終出現碎片化的頻率比CMS收集器小得多的原因——以這種方式回收對象,實際上伴隨著針對當前分區的壓縮。

2. 兩個模式

G1收集器的模式主要有兩種:

  • Young GC(新生代垃圾收集)
  • Mixed GC(混合垃圾收集)

在R大的帖子中,給出了一個假象的G1垃圾收集運行過程,如下圖所示,在結合上一小節的細節,就可以將G1 GC的正常過程理解清楚了。image.png

3. 巨型對象的管理

巨型對象:在G1中,如果一個對象的大小超過分區大小的一半,該對象就被定義為巨型對象(Humongous Object)。巨型對象時直接分配到老年代分區,如果一個對象的大小超過一個分區的大小,那麼會直接在老年代分配兩個連續的分區來存放該巨型對象。巨型分區一定是連續的,分配之後也不會被移動——沒啥益處。

由於巨型對象的存在,G1的堆中的分區就分成了三種類型:新生代分區、老年代分區和巨型分區,如下圖所示:image.png

如果一個巨型對象跨越兩個分區,開始的那個分區被稱為“開始巨型”,後面的分區被稱為“連續巨型”,這樣最後一個分區的一部分空間是被浪費掉的,如果有很多巨型對象都剛好比分區大小多一點,就會造成很多空間的浪費,從而導致堆的碎片化。如果你發現有很多由於巨型對象分配引起的連續的併發周期,並且堆已經碎片化(明明空間夠,但是觸發了FULL GC),可以考慮調整-XX:G1HeapRegionSize參數,減少或消除巨型對象的分配。

關於巨型對象的回收:在JDK8u40之前,巨型對象的回收只能在併發收集周期的清除階段或FULL GC過程中過程中被回收,在JDK8u40(包括這個版本)之後,一旦沒有任何其他對象引用巨型對象,那麼巨型對象也可以在年輕代收集中被回收。

4. G1執行過程中的異常情況

併發標記周期開始後的FULL GC

G1啟動了標記周期,但是在併發標記完成之前,就發生了Full GC,日誌常常如下所示:

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]

GC concurrent-mark-start開始之後就發生了FULL GC,這說明針對老年代分區的回收速度比較慢,或者說對象過快得從新生代晉升到老年代,或者說是有很多大對象直接在老年代分配。針對上述原因,我們可能需要做的調整有:調大整個堆的大小、更快得觸發併發回收周期、讓更多的回收線程參與到垃圾收集的動作中。

混合收集模式中的FULL GC

在GC日誌中觀察到,在一次混合收集之後跟著一條FULL GC,這意味著混合收集的速度太慢,在老年代釋放出足夠多的分區之前,應用程式就來請求比當前剩餘可分配空間大的記憶體。針對這種情況我們可以做的調整:增加每次混合收集收集掉的老年代分區個數;增加併發標記的線程數;提高混合收集發生的頻率。

疏散失敗(轉移失敗)

在新生代垃圾收集快結束時,找不到可用的分區接收存活下來的對象,常見如下的日誌:

60.238: [GC pause (young) (to-space overflow), 0.41546900 secs]

這意味著整個堆的碎片化已經非常嚴重了,我們可以從以下幾個方面調整:(1)增加整個堆的大小——通過增加-XX:G1ReservePercent選項的值(並相應增加總的堆大小),為“目標空間”增加預留記憶體量;(2)通過減少 -XX:InitiatingHeapOccupancyPercent提前啟動標記周期;(3)
你也可以通過增加-XX:ConcGCThreads選項的值來增加併發標記線程的數目;

巨型對象分配失敗

如果在GC日誌中看到莫名其妙的FULL GC日誌,又對應不到上述講過的幾種情況,那麼就可以懷疑是巨型對象分配導致的,這裡我們可以考慮使用jmap命令進行堆dump,然後通過MAT對堆轉儲文件進行分析。關於堆轉儲文件的分析技巧,後續會有專門的文章介紹。

四、G1的調優

G1的調優目標主要是在避免FULL GC和疏散失敗的前提下,儘量實現較短的停頓時間和較高的吞吐量。關於G1 GC的調優,需要記住以下幾點:

  1. 不要自己顯式設置新生代的大小(用Xmn-XX:NewRatio參數),如果顯式設置新生代的大小,會導致目標時間這個參數失效。

  2. 由於G1收集器自身已經有一套預測和調整機制了,因此我們首先的選擇是相信它,即調整-XX:MaxGCPauseMillis=N參數,這也符合G1的目的——讓GC調優儘量簡單,這裡有個取捨:如果減小這個參數的值,就意味著會調小新生代的大小,也會導致新生代GC發生得更頻繁,同時,還會導致混合收集周期中回收的老年代分區減少,從而增加FULL GC的風險。這個時間設置得越短,應用的吞吐量也會受到影響。

  3. 針對混合垃圾收集的調優。如果調整這期望的最大暫停時間這個參數還是無法解決問題,即在日誌中仍然可以看到FULL GC的現象,那麼就需要自己手動做一些調整,可以做的調整包括:

  • 調整G1垃圾收集的後臺線程數,通過設置-XX:ConcGCThreads=n這個參數,可以增加後臺標記線程的數量,幫G1贏得這場你追我趕的游戲;
  • 調整G1垃圾收集器併發周期的頻率,如果讓G1更早得啟動垃圾收集,也可以幫助G1贏得這場比賽,那麼可以通過設置-XX:InitiatingHeapOccupancyPercent這個參數來實現這個目標,如果將這個參數調小,G1就會更早得觸發併發垃圾收集周期。這個值需要謹慎設置:如果這個參數設置得太高,會導致FULL GC出現得頻繁;如果這個值設置得過小,又會導致G1頻繁得進行併發收集,白白浪費CPU資源。通過GC日誌可以通過一個點來判斷GC是否正常——在一輪併發周期結束後,需要確保堆剩下的空間小於InitiatingHeapOccupancyPercent的值。
  • 調整G1垃圾收集器的混合收集的工作量,即在一次混合垃圾收集中儘量多處理一些分區,可以從另外一方面提高混合垃圾收集的頻率。在一次混合收集中可以回收多少分區,取決於三個因素:(1)有多少個分區被認定為垃圾分區,-XX:G1MixedGCLiveThresholdPercent=n這個參數表示如果一個分區中的存活對象比例超過n,就不會被挑選為垃圾分區,因此可以通過這個參數控制每次混合收集的分區個數,這個參數的值越大,某個分區越容易被當做是垃圾分區;(2)G1在一個併發周期中,最多經歷幾次混合收集周期,這個可以通過-XX:G1MixedGCCountTarget=n設置,預設是8,如果減小這個值,可以增加每次混合收集收集的分區數,但是可能會導致停頓時間過長;(3)期望的GC停頓的最大值,由MaxGCPauseMillis參數確定,預設值是200ms,在混合收集周期內的停頓時間是向上規整的,如果實際運行時間比這個參數小,那麼G1就能收集更多的分區。

五、G1的最佳實踐

1. 關鍵參數項

  • -XX:+UseG1GC,告訴JVM使用G1垃圾收集器
  • -XX:MaxGCPauseMillis=200,設置GC暫停時間的目標最大值,這是個柔性的目標,JVM會儘力達到這個目標
  • -XX:INitiatingHeapOccupancyPercent=45,如果整個堆的使用率超過這個值,G1會觸發一次併發周期。記住這裡針對的是整個堆空間的比例,而不是某個分代的比例。

2. 最佳實踐

不要設置年輕代的大小

通過-Xmn顯式設置年輕代的大小,會幹擾G1收集器的預設行為:

  • G1不再以設定的暫停時間為目標,換句話說,如果設置了年輕代的大小,就無法實現自適應的調整來達到指定的暫停時間這個目標
  • G1不能按需擴大或縮小年輕代的大小

響應時間度量

不要根據平均響應時間(ART)來設置-XX:MaxGCPauseMillis=n這個參數,應該設置希望90%的GC都可以達到的暫停時間。這意味著90%的用戶請求不會超過這個響應時間,記住,這個值是一個目標,但是G1並不保證100%的GC暫停時間都可以達到這個目標

3. G1 GC的參數選項

參數名 含義 預設值
-XX:+UseG1GC 使用G1收集器 JDK1.8中還需要顯式指定
-XX:MaxGCPauseMillis=n 設置一個期望的最大GC暫停時間,這是一個柔性的目標,JVM會儘力去達到這個目標 200
-XX:InitiatingHeapOccupancyPercent=n 當整個堆的空間使用百分比超過這個值時,就會觸發一次併發收集周期,記住是整個堆 45
-XX:NewRatio=n 新生代和老年代的比例 2
-XX:SurvivorRatio=n Eden空間和Survivor空間的比例 8
-XX:MaxTenuringThreshold=n 對象在新生代中經歷的最多的新生代收集,或者說最大的歲數 G1中是15
-XX:ParallelGCThreads=n 設置垃圾收集器的並行階段的垃圾收集線程數 不同的平臺有不同的值
-XX:ConcGCThreads=n 設置垃圾收集器併發執行GC的線程數 n一般是ParallelGCThreads的四分之一
-XX:G1ReservePercent=n 設置作為空閑空間的預留記憶體百分比,以降低目標空間溢出(疏散失敗)的風險。預設值是 10%。增加或減少這個值,請確保對總的 Java 堆調整相同的量 10
-XX:G1HeapRegionSize=n 分區的大小 堆記憶體大小的1/2000,單位是MB,值是2的冪,範圍是1MB到32MB之間
-XX:G1HeapWastePercent=n 設置您願意浪費的堆百分比。如果可回收百分比小於堆廢物百分比,JavaHotSpotVM不會啟動混合垃圾回收周期(註意,這個參數可以用於調整混合收集的頻率)。 JDK1.8是5
-XX:G1MixedGCCountTarget=8 設置併發周期後需要執行多少次混合收集,如果混合收集中STW的時間過長,可以考慮增大這個參數。(註意:這個可以用來調整每次混合收集中回收掉老年代分區的多少,即調節混合收集的停頓時間) 8
-XX:G1MixedGCLiveThresholdPercent=n 一個分區是否會被放入mix GC的CSet的閾值。對於一個分區來說,它的存活對象率如果超過這個比例,則改分區不會被列入mixed gc的CSet中 JDK1.6和1.7是65,JDK1.8是85

常見問題

  1. Young GC、Mixed GC和Full GC的區別?
    答:Young GC的CSet中只包括年輕代的分區,Mixed GC的CSet中除了包括年輕代分區,還包括老年代分區;Full GC會暫停整個引用,同時對新生代和老年代進行收集和壓縮。

  2. ParallelGCThreads和ConcGCThreads的區別?
    答:ParallelGCThreads指得是在STW階段,並行執行垃圾收集動作的線程數,ParallelGCThreads的值一般等於邏輯CPU核數,如果CPU核數大於8,則設置為5/8 * cpus,在SPARC等大型機上這個繫數是5/16。;ConcGCThreads指的是在併發標記階段,併發執行標記的線程數,一般設置為ParallelGCThreads的四分之一。

  3. write barrier在GC中的作用?如何理解G1 GC中write barrier的作用?
    寫屏障是一種記憶體管理機制,用在這樣的場景——當代碼嘗試修改一個對象的引用時,在前面放上寫屏障就意味著將這個對象放在了寫屏障後面。write barrier在GC中的作用有點複雜,我們這裡以trace GC演算法為例講下:trace GC有些演算法是併發的,例如CMS和G1,即用戶線程和垃圾收集線程可以同時運行,即mutator一邊跑,collector一邊收集。這裡有一個限制是:黑色的對象不應該指向任何白色的對象。如果mutator視圖讓一個黑色的對象指向一個白色的對象,這個限制就會被打破,然後GC就會失敗。針對這個問題有兩種解決思路:(1)通過添加read barriers阻止mutator看到白色的對象;(2)通過write barrier阻止mutator修改一個黑色的對象,讓它指向一個白色的對象。write barrier的解決方法就是講黑色的對象放到寫write barrier後面。如果真得發生了white-on-black這種寫需求,一般也有多種修正方法:增量得將白色的對象變灰,將黑色的對象重新置灰等等。我理解,增量的變灰就是CMS和G1里併發標記的過程,將黑色的對象重新變灰就是利用卡表或SATB的緩衝區將黑色的對象重新置灰的過程,當然會在重新標記中將所有灰色的對象處理掉。關於G1中write barrier的作用,可以參考R大的這個帖子里提到的:image.png

  4. G1里在併發標記的時候,如果有對象的引用修改,要將舊的值寫到一個緩衝區中,這個動作前後會有一個write barrier,這段可否細說下?
    答:這塊涉及到SATB標記演算法的原理,SATB是指start at the beginning,即在併發收集周期的第一個階段(初始標記)是STW的,會給所有的分區做個快照,後面的掃描都是按照這個快照進行;在併發標記周期的第二個階段,併發標記,這是收集線程和應用線程同時進行的,這時候應用線程就可能修改了某些引用的值,導致上面那個快照不是完整的,因此G1就想了個辦法,我把在這個期間對對象引用的修改都記錄動作都記錄下來,有點像mysql的操作日誌。

  5. GC演算法中的三色標記演算法怎麼理解?
    trace GC將對象分為三類:白色(垃圾收集器未探測到的對象)、灰色(活著的對象,但是依然沒有被垃圾收集器掃描過)、黑色(活著的對象,並且已經被垃圾收集器掃描過)。垃圾收集器的工作過程,就是通過灰色對象的指針掃描它指向的白色對象,如果找到一個白色對象,就將它設置為灰色,如果某個灰色對象的可達對象已經全部找完,就將它設置為黑色對象。當在當前集合中找不到灰色的對象時,就說明該集合的回收動作完成,然後所有白色的對象的都會被回收。PS:這個問題來自參考資料17,我將原文也貼在下麵:

    For a tracing collector (marking or copying), one conceptually colours the data white (not yet seen by the collector), black (alive and scanned by the collector) and grey (alive but not yet scanned by the collector). The collector proceeds by scanning grey objects for pointers to white objects. The white objects found are turned grey, and the grey objects scanned are turned black. When there are no more grey objects, the collection is complete and all the white objects can be recycled.

參考資料

  1. Understanding G1 GC Logs
  2. Garbage First Garbage Collector Tuning
  3. 垃圾優先型回收器調優
  4. Oracle的GC調優文檔——G1
  5. The Garbage-First Garbage Collector
  6. 《Java性能權威指南》
  7. 《Java性能調優指南》
  8. G1入門,O記官網的PPT
  9. Java Hotspot G1 GC的一些關鍵技術
  10. G1 GC的論文
  11. R大關於G1 GC的帖子
  12. Tips for Tuning the Garbage First Garbage Collector
  13. Java性能調優指南
  14. Java性能權威指南
  15. G1: What are the differences between mixed gc and full gc?
  16. Part 1: Introduction to the G1 Garbage Collector
  17. Collecting and reading G1 garbage collector logs - part 2
  18. GC FAQ -- algorithms

本號專註於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。
javaadu


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 系列鏈接地址: 深入理解設計模式 系列目錄 一、產品汪的神助攻,代碼狗的安慰劑 定義:設計模式,指的是一個場景(context)下的一種解決方法(Solution),只要大家都認可某種模式,那麼就只需要很短的一個名字,就可以代替很多很多的語言和文字交流,如果你覺得設計模式降低生產效率,那隻能說你在這 ...
  • 類是不是越小越好?最近在讀John Ousterhout的《A Philosophy of Software Design》,感到作者文筆流暢,書中內容具有啟發性。這裡摘要一部分內容,以供參考、學習。 本文鏈接:https://www.cnblogs.com/hhelibeb/p/10708951. ...
  • 多態的概述多態是繼封裝、繼承之後,面向對象的第三大特性。現實事物經常會體現出多種形態,如學生,學生是人的一種,則一個具體的同學張三既是學生也是人,即出現兩種形態。 Java作為面向對象的語言,同樣可以描述一個事物的多種形態。如Student類繼承了Person類,一個Student的對象便既是Stu... ...
  • URL監控埋點作用 一個http請求來了之後,會自動打點,能夠記錄每個url的訪問情況,並將以此請求後續的調用鏈路串起來,可以在cat上查看logview 可以在cat Transaction及Event 頁面上都看到URL和URL.Forward(如果有Forward請求的話)兩類數據;Trans ...
  • 寫代碼也可以進行資源整合,實現相應的功能,達到預期的目標即可。 ...
  • 前言 最近在做工作流的事情,正好有個需求,要添加一個附件上傳的功能,曾找過不少上傳插件,都不是特別滿意。無意中發現一個很好用的開源web文件管理器插件 elfinder,功能比較完善,社區也很活躍,還方便二次開發。 環境搭建 | 軟體 | 地址| | | | | SpringBoot| https: ...
  • 圖片比對 昨天的博客已經將圖片存儲到了本地,今天要做的第一件事情,就是需要在兩張圖片中進行比對,將圖片缺口定位出來 缺口圖片 完整圖片 計算缺口坐標 對比兩張圖片的所有RBG像素點,得到不一樣像素點的x值,即要移動的距離 極驗證對於用戶行為檢測是有專門的演算法的,找到一篇比較老的文章 https:// ...
  • 重構現有代碼:Refactoring 1.WHY SHOULD WE REFACTOR? 1.Refactoring Improves the Design of Software Without refactoring, the internal design—the architecture—o ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...