史上最全GC原理

史上最全GC原理

什麼是垃圾

定義

  • 釋放已佔用的內存,防止內存泄露
  • 清除已經死亡或者長時間未使用的對象內存

語言特性

  • c++手動回收垃圾

    • 忘記回收
    • 回收多次
  • java 自動回收

如何定位垃圾

引用計數法

  • 對象頭中分配一片空間用於存儲對象引用次數
  • 程序執行過程中完成,非STW
  • 注意:Recycler 算法可解決循環引用,但在多線程環境下,引用計數變更也要進行昂貴的同步操作,性能較低,早期的編程語言會採用此算法

根可達性分析算法

  • GC Root

    • 虛擬機棧中引用的對象

      public static void testGC(){
      StackLocalParameter s = new StackLocalParameter(“localParameter”);
      s = null;
      }


    • 方法區中類靜態屬性引用的變量

    • 方法區中常量引用的對象

    • 本地方法棧JNI中引用的對象

      任何 native 接口都會使用某種本地方法棧,實現的本地方法接口是使用 C 連接模型的話,那麼它的本地方法棧就是 C 棧。當線程調用 Java 方法時,虛擬機會創建一個新的棧幀並壓入 Java 棧。然而當它調用的是本地方法時,虛擬機會保持 Java 棧不變,不再在線程的 Java 棧中壓入新的幀,虛擬機只是簡單地動態連接並直接調用指定的本地方法。

  • 通過GC roots對象作爲起點開始向下搜索引用的對象,找到的對象都爲存活對象即可達,其他沒有標記的對象都爲垃圾

怎麼回收垃圾

有哪些回收方法

  • 標記清除 mark sweep

    • 1、從GC Root遍歷對象圖,標記出垃圾對象;2、再次遍歷清除
    • 產生碎片,內存不連續,效率偏低(兩遍掃描)
  • 複製copying

    • 1、內存分爲兩塊;2、第一塊使用完成後將存活的對象複製到第二塊;3、清除第一塊內存;
    • 沒有碎片,效率高,但浪費空間,大對象時複製成本較高
  • 標記整理 mark compact

    • 1、標記出所有存活對象;2、對存活對象按照整理順序(Compaction Order)整理到內存的一端;3、清理端以外的內存
    • 沒有碎片、無浪費空間,但效率偏低(兩遍掃描,引用指針需要調整,內存變動頻繁)
  • 分代算法Generational Collection

    • java堆空間

      • 新生代1/3

        • Eden區8/10

          • 98%的對象朝生夕死
        • From區1/10

        • To區1/10

      • 老年代2/3

        • 哪些對象會進入

          • 大對象

            • 需要大量連續內存空間的對象,避免在新生代產生大量複製
          • 長期存活對象

            • 對象頭中存放對象年齡,每經過一次minorgc年齡增加一次,默認到15時會進入

              • 可配置:MaxTenuringThreshold
          • 動態對象年齡

            • 年齡1的佔用了33%,年齡2的佔用了33%,累加和超過默認的TargetSurvivorRatio(50%),年齡2和年齡3的對象都要晉升

              • 有點負載均衡感覺
        • 常用算法

    • 是以上三種回收算法的組合算法

jvm中有哪些收集器

  • Serial old

  • 分代收集器

    • ParNew

      • 採用複製算法的多線程收集器
      • 主要工作在 Young 區,可以通過 -XX:ParallelGCThreads 參數來控制收集的線程數,整個過程都是 STW 的,常與 CMS 組合使用。
    • CMS

      • 目標:獲取最短回收停頓時間

      • 算法:三色標記+標記清除算法+增量更新算法

      • 步驟

        • 1、初始標記:STW,標記GC Root直接引用的對象;
          2、併發標記:從GC Root引用對象開始遍歷對象圖,標記出可達對象;;
          3、重新標記:STW,採用增量更新算法重新標記2步因用戶線程增加引用的對象;
          4、併發清理:清理未標記的垃圾對象;
          5、併發重置:重置本次GC過程中的標記數據;



      • 問題

        • 併發

          • 搶佔用戶線程cpu資源

            • CMS默認回收線程數是(CPU個數+3)/4

              這個公式的意思是當CPU大於4個時,保證回收線程佔用至少25%的CPU資源,這樣用戶線程佔用75%的CPU,這是可以接受的。

              但是,如果CPU資源很少,比如只有兩個的時候怎麼辦?按照上面的公式,CMS會啓動1個GC線程。相當於GC線程佔用了50%的CPU資源,這就可能導致用戶程序的執行速度忽然降低了50%,50%已經是很明顯的降低了。

            • 解決辦法:incremental mode(增量模式),執行過程中GC線程和用戶線程交替執行

          • 浮動垃圾

            • 併發清理過程中產生浮動垃圾,可以忽略,下次清理

            • 解決辦法

              • 提前回收機制:CMSInitiatingOccupancyFraction參數默認是內存佔用92%時啓動GC

                • 如果設置99%,這是需要內存分配1%時會Concurrent Mode Failure錯誤,這是CMS默認啓動Serial Old收集器,效率更慢
              • 動態檢查機制:UseCMSInitiatingOccupancyOnlyCMS參數設置CMS會根據歷史記錄,預測老年代還需要多久填滿及進行一次回收所需要的時間。在老年代空間用完之前,CMS可以根據自己的預測自動執行垃圾回收。

          • GC執行過程不確定

            • 在併發標記或者清理階段會出現還沒有回收完成又一次觸發fullgc,這時會出現concurrent mode failure錯誤,此時會STW,用戶serioal old處理
        • 標記清除算法

          • 產生碎片化內存,分配效率慢

            • 解決辦法

              • UseCMSCompactAtFullCollection參數(默認開啓),在Full GC後開啓內存碎片整理,但是STW
              • XX:CMSFullGCsBeforeCompaction,參數表示經歷多少次fullgc後對內存空間壓縮整理,默認爲0,每次fullgc會壓縮
      • 最佳實踐配置

        • -XX:+UseConcMarkSweepGC
          -XX:CMSInitiatingOccupancyFraction=80 //回收內存佔比
          -XX:+UseCMSInitiatingOccupancyOnly //啓動動態檢查機制
          -XX:CMSFullGCsBeforeCompaction=5//設置經歷多少次fc後開始壓縮整理碎片


      • 應用場景

        • 多數應用於互聯網站或者 B/S 系統的服務器端上,JDK9 被標記棄用,JDK14 被刪除
  • 分區收集器

    • G1

      G1收集器在後臺維護了一個優先列表,每次根據允許的收集時間,優先選擇回收價值最大的Region(這也就是它的名字Garbage-First的由來),比如一個Region花200ms能回收10M垃圾,另外一個Region花50ms能回收20M垃圾,在回收時間有限情況下,G1當然會優先選擇後面這個Region回收。==這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限時間內可以儘可能高的收集效率

      • 目標:針對大內存、達到實時高效、高吞吐量

      • 算法:三色標記+複製+標記壓縮+STAB

      • 基本概念

        • 分區region

          • 物理分區,邏輯分代,內存區域分爲E O H S等,每個分代內存可以不連續

            E代表是Eden區,S代表Survivor,O代表Old區,H代表humongous表示巨型對象(大小大小Region空間一半的對象)

          • 單個分區取值1-32M,必須是2的冪次,-XX:G1HeapRegionSize

          • 最多有2048個分區

        • SATB

          • Snapshot-At-The-Beginning,GC初始標記階段對堆內存活的對象做一次快照,作用是維持併發GC的正確性

          • 如何保證正確性

            • Region中有兩個top-at-mark-start(TAMS)指針,分別爲prevTAMS(上一次標記的位置)和nextTAMS(下次標記的位置)。在TAMS以上的對象是新分配的,這是一種隱式的標記

              • 解決了併發期間新對象分配
            • 對象的引用被替換時,通過write barrier對引用字段複製進行環切AOP, 將舊引用記錄下來,所以效率會低些,然後在最終標記階段只掃描出有write barrier記錄的對象

              • 解決了灰色對象到白色對象的引用斷開
          • 問題:如果被替換的白對象就是要被收集的垃圾,這次的標記會讓它躲過GC,這就是float garbage,STAB精度偏低

        • 寫屏障

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

        • RSet

          • Remember Set,每個分區中維護一個RSet,主要記錄其他分區引用本分區對象的關係,誰引用了我的對象

            • 如何輔助GC

              • YGC時,選定Y區的RSet作爲根集,裏面記錄old->young的跨帶引用,避免掃描整個old代區
              • mixed gc時,old代中每個分區記錄old->old,young->old的RSet,不用掃描整個old分代區
        • CSet

          • Collection Set,GC要收集的Region的集合(任意分代),跨分區的掃描RSet
        • 停頓預測模型

          • 通過模型統計計算出的歷史數據來預測本次回收需要選擇的Region數量,儘量滿足設置的目標
          • 通過XX:MaxGCPauseMillis參數設置用戶期望的停頓時間,默認200ms
          • 衰減標準偏差爲理論基礎
      • GC模式

        • Young GC

          • E區無法分配內存(達到閾值)時啓動,即E區和S區複製到Old區(MaxTenuringThreshold參數配置)或者另外一個S區,多線程並行執行

            YoungGC的回收過程如下:

            根掃描,跟CMS類似,Stop the world,掃描GC Roots對象。
            處理Dirty card,更新RSet.
            掃描RSet,掃描RSet中所有old區對掃描到的young區或者survivor去的引用。
            拷貝掃描出的存活的對象到survivor2/old區
            處理引用隊列,軟引用,弱引用,虛引用(下一篇優化中會再講一下這三種引用對gc的影響)



        • Mixed GC

          • 只回收老年代部分region,一般發生在YGC之後,目的是複用YGC掃描的GC Root,減少stw

          • 發生時機

            • G1MixedGCLiveThresholdPercent參數控制老年代分區中的存活對象比例,達到閾值這個分區會放在RSet中,默認45
            • G1HeapWastePercent參數控制,在一次younggc之後,可以允許的堆垃圾百佔比,超過這個值就會觸發mixedGC
          • 步驟

            • 1、初始標記:標記GC Roots,會STW,複用YoungGC的暫停時間,設置好所有分區的NTAMS值

            • 2、根分區掃描(RootRegionScan)

              • 和java程序並行執行,基於標記算法,對Survivor對象全部掃描標記爲gcroot
            • 3、併發標記:從GC Root引用對象開始遍歷對象樹,標記出存活對象

            • 4、最終標記:會STW,標記出在3階段發生變化的對象,同時處理STAB緩衝區;

            • 5、清除:STW,清除標記的垃圾對象,清理之後,將存活對象複製到其他可用分區,主要解決內存碎片問題

              • 1、對各個Region的回收價值和成本進行排序,根據用戶設置的停頓時間執行清除計劃

                比如說老年代此時有1000個Region都滿了,但是因爲根據預期停頓時間,本次垃圾回收可能只能停頓200毫秒,那麼通過之前回收成本計算得知,可能回收其中800個Region剛好需要200ms,那麼就只會回收800個Region(Collection Set,要回收的集合),儘量把GC導致的停頓時間控制在我們指定的範圍內

              • 2、採用複製算法,將一個region中的存活對象複製到另一個空的region,這樣好處在於不會產生碎片

      • 使用場景

        • 服務端垃圾收集器

        • 多處理器,內存偏大,一般大於6G以上

        • 需要低延遲的響應(停頓時間可控)

        • 存在以下情況可以嘗試使用G1

          • Full GC 次數太頻繁或者消耗時間太長
          • 對象分配的頻率或代數提升(promotion)顯著變化
          • 受夠了太長的垃圾回收或內存整理時間(超過0.5~1秒)
      • 和CMS的區別

        • 停頓時間可控
        • 最終標記效率更高,G1只標記寫屏障記錄的對象,CMS Remark階段掃描所有對象,STW時間更長
        • CMS清除階段是併發的,G1是STW
      • 最佳實踐配置

        • -XX:+UseG1GC
          -XX:MaxGCPauseMillis=200 //設置停頓時間,默認200
          -XX:INitiatingHeapOccupancyPercent=45 //設置整個堆使用率超過設置的值時啓動Mix GC,默認45

        • 不要設置年輕代的大小

          通過-Xmn顯式設置年輕代的大小,會干擾G1收集器的默認行爲:

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

        • 響應時間度量

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

        • -XX:ParallelGCThreads=n //垃圾收集器的並行階段的垃圾收集線程數

        • -XX:ConcGCThreads=n //垃圾收集器併發執行GC的線程數

    • ZGC

    • Shenandoah

  • 三色標記法

    • 含義

      • 黑:對象和屬性引用的對象已完成標記
      • 灰:對象被標記,但屬性資源引用的對象沒有標記完成
      • 白:對象沒有被標記,回收對象
    • 問題

      • 漏標(兩者缺一不可)

        • Mutator將黑對象引用指向白對象
        • Mutator刪除灰對象到白對象的直接或者間接引用
      • 解決辦法

        • 核心是解決其中一步即可

          • CMS 增量更新+寫屏障
            黑對象新增白對象引用時通過寫屏障記錄下來,在重新標記階段對記錄的從新標記,即黑色對象變爲灰色對象
          • G1 Shenandoah STAB+寫屏障
            灰色對象刪除了白色對象引用時,通過寫屏障記錄下來,然後重新標記階段再次標記
          • ZGC 讀屏障(待學習和補充)

XMind - Trial Version

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章