1. Stop The World 是何時發(fā)生的?

        共 4220字,需瀏覽 9分鐘

         ·

        2021-08-20 22:41

        垃圾回收流程的一些流程

        哪些對象是垃圾?

        當我們進行垃圾回收的時候,首先需要判斷哪些對象是存活的?

        常用的方法有如下兩種

        1. 引用計數法
        2. 可達性分析法

        Python判斷對象存活的算法用的是引用計數法,而Java則使用的是可達性分析法。

        「通過GC ROOT可達的對象,不能被回收,不可達的對象則可以被回收,搜索走過的路徑叫做引用鏈」

        不可達對象會進行2次標記的過程,通過GC ROOT不可達,會被第一次標記。如果需要執(zhí)行finalize()方法,則這個對象會被放入一個隊列中執(zhí)行finalize(),如果在finalize()方法中成功和引用鏈上的其他對象關聯,則會被移除可回收對象集合(「一般你不建議你使用finalize方法」),否則被回收

        「常見的GC ROOT有如下幾種」

        1. 虛擬機棧(棧幀中的本地變量表)中引用的對象
        2. 方法區(qū)中類靜態(tài)屬性引用的對象
        3. 方法區(qū)中常量引用的對象
        4. 本地方法棧中JNI(Native方法)引用的對象

        「照這樣看,程序中的GC ROOT有很多,每次垃圾回收都要對GC ROOT的引用鏈分析一遍,感覺耗費的時間很長啊,有沒有可能減少每次掃描的GC ROOT?」

        分代和跨代引用

        其實當前虛擬機大多數都遵循了“分代收集”理論進行設計,它的實現基于2個分代假說之上

        1. 絕大多數對象都是朝生夕滅的
        2. 熬過多次垃圾收集過程的對象就越難以消亡

        因此堆一般被分為新生代和老年代,針對新生代的GC叫MinorGC,針對老年代的GC叫OldGC。但是分代后有一個問題,為了找到新生代的存活對象,不得不遍歷老年代,反過來也一樣當進行MinorGC的時候,如果我們只遍歷新生代,那么可以判定ABCD為存活對象。但是E不會被判斷為存活對象,所以就會有問題。

        為了解決這種跨代引用的對象,最笨的辦法就是遍歷老年代的對象,找出這些跨代引用的對象。但這種方式對性能影響較大

        這時就不得不提到第三個假說

        「跨代引用相對于同代引用來說僅占極少數?!?/strong>

        根據這條假說,我們就不需要為了少量的跨代引用去掃描整個老年代。「為了避免遍歷老年代的性能開銷,垃圾回收器會引入一種記憶集的技術,記憶集就是用來記錄跨代引用的表」

        如新生代的記憶集就保存了老年代持有新生代的引用關系

        所以在進行MinorGC的時候,只需要將包含跨代引用的內存區(qū)域加入GC ROOT一起掃描就行了

        卡表

        前面我們說到垃圾收集器用記憶集來記錄跨代引用。其實你可以把記憶集理解為接口,卡表理解為實現,類比Map和HashMap。

        卡表最簡單的形式可以只是一個字節(jié)數組, 而HotSpot虛擬機確實也是這樣做的。以下這行代碼是HotSpot默認的卡表標記邏輯:

        CARD_TABLE [this address >> 9] = 0;

        HotSpot用一個數組元素來保存對應的內存地址是有有跨代引用對象(從this address右移9位可以看出每個元素映射了512字節(jié)的內存)

        當數組元素值為0時表明對應的內存地址不存在跨代引用對象,否則存在(稱為卡表中這個元素變臟)

        如何更新卡表?

        「將卡表元素變臟的過程,HotSpot是通過寫屏障來實現的」,即當其他代對象引用當前分代對象的時候,在引用賦值階段更新卡表,具體實現方式類似于AOP

        void oop_field_store(oop* field, oop new_value) 
        // 引用字段賦值操作
        *field = new_value;
        // 寫后屏障,在這里完成卡表狀態(tài)更新 
        post_write_barrier(field, new_value);
        }

        三色標記法

        執(zhí)行思路

        「如何判斷一個對象可達呢?這就不得不提到三色標記法」

        白色:剛開始遍歷的時候所有對象都是白色的 灰色:被垃圾回收器訪問過,但至少還有一個引用未被訪問 黑色:被垃圾回收器訪問過,并且這個對象的所有引用都被訪問過,是安全存活的對象(GC ROOT會被標記為黑色)

        以上圖為例,三色標記法的執(zhí)行流程如下

        1. 先將GC ROOT引用的對象B和E標記為灰色
        2. 接著將B和E引用的對象A,C和F標記為灰色,此時B和E標記為黑色
        3. 依次類推,最終被標記為白色的對象需要被回收

        三色標記法問題

        可達性分析算法根節(jié)點枚舉這一步必須要在一個能保障一致性的快照中分析,所以要暫停用戶線程(Stop The World ,STW),在各種優(yōu)化技巧的加持下,停頓時間已經非常短了。

        在從根節(jié)點掃描的過程則不需要STW,但是也會發(fā)生一些問題。由于此時垃圾回收線程和用戶線程一直運行,所以引用關系會發(fā)生變化

        1. 應該被回收的對象被標記為不被回收
        2. 不應該被回收的對象標記為應該回收

        第一種情況影響不大,大不了后續(xù)回收即可。但是第二種情況則會造成致命錯誤

        所以經過研究表明,只有同時滿足兩個條件才會發(fā)生第二種情況

        1. 插入了一條或者多條黑色到白色對象的引用
        2. 刪除了全部從灰色到白色對象的引用

        為了解決這個問題,我們破壞2個條件中任意一個不就行了,由此產生了2中解決方案,「增量更新」「原始快照」。CMS使用的是增量更新,G1使用的是原始快照

        「增量更新要破壞的是第一個條件」, 當黑色對象插入新的指向白色對象的引用關系時, 就將這個新插入的引用記錄下來, 等并發(fā)掃描結束之后, 再將這些記錄過的引用關系中的黑色對象為根, 重新掃描一次。這可以簡化理解為, 黑色對象一旦新插入了指向白色對象的引用之后, 它就變回灰色對象了

        「原始快照要破壞的是第二個條件」, 當灰色對象要刪除指向白色對象的引用關系時, 就將這個要刪除的引用記錄下來, 在并發(fā)掃描結束之后, 再將這些記錄過的引用關系中的灰色對象為根, 重新掃描一次。這也可以簡化理解為, 無論引用關系刪除與否, 都會按照剛剛開始掃描那一刻的對象圖快照來進行搜索。

        參考自《深入理解Java虛擬機》

        垃圾收集器

        圖中展示了七種作用于不同分代的收集器,如果兩個收集器之間存在連線,就說明它們可以搭配使用。在JDK8時將Serial+CMS,ParNew+Serial Old這兩個組合聲明為廢棄,并在JDK9中完全取消了這些組合的支持

        并行和并發(fā)都是并發(fā)編程中的專業(yè)名詞,在談論垃圾收集器的上下文語境中, 它們可以理解為

        「并行(Parallel)」:指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態(tài)

        「并發(fā)(Concurrent」):指用戶線程與垃圾收集線程同時執(zhí)行

        Serial收集器

        「新生代,標記-復制算法,單線程。進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束」

        ParNew收集器

        「ParNew本質上是Serial收集器的多線程并行版本」

        Parallel Scavenge收集器

        「新生代,標記復制算法,多線程,主要關注吞吐量」

        吞吐量=運行用戶代碼時間/(運行用戶代碼時間+運行垃圾收集時間)

        Serial Old收集器

        「老年代,標記-整理算法,單線程,是Serial收集器的老年代版本」

        用處有如下2個

        1. 在JDK5以及之前的版本中與Parallel Scavenge收集器搭配使用
        2. 作為CMS收集器發(fā)生失敗時的后備預案,在并發(fā)收集發(fā)生Concurrent Mode Failure時使用

        Parallel Old收集器

        「老年代,標記-整理算法,多線程,是Parallel Scavenge收集器的老年代版本」

        在注重吞吐量或者處理器資源較為稀缺的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個組合

        CMS收集器

        「老年代,標記-清除算法,多線程,主要關注延遲」

        運作過程分為4個步驟

        1. 初始標記(CMS initial mark)
        2. 并發(fā)標記(CMS concurrent mark)
        3. 重新標記(CMS remark)
        4. 并發(fā)清除(CMS concurrent sweep)
        1. 初始標記:標記一下GC Roots能直接關聯到的對象,速度很快(這一步會發(fā)生STW)
        2. 并發(fā)標記:從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集一起并發(fā)運行
        3. 重新標記:為了修正并發(fā)標記期間,因用戶程序繼續(xù)運作而導致標記產生變動的那一部分對象的標記記錄(「就是三色標記法中的增量更新」,這一步也會發(fā)生STW)
        4. 并發(fā)清除:清理刪除掉標記階段判斷的已經死亡的對象,由于不需要移動存活對象,所以看這個階段也是可以與用戶線程同時并發(fā)的

        總結

        收集器收集對象和算法收集器類型說明適用場景
        Serial新生代,復制算法單線程
        簡單高效;適合內存不大的情況
        ParNew新生代,復制算法并行的多線程收集器ParNew垃圾收集器是Serial收集器的多線程版本搭配CMS垃圾回收器的首選
        Parallel Scavenge吞吐量優(yōu)先收集器新生代,復制算法并行的多線程收集器類似ParNew,更加關注吞吐量,達到一個可控制的吞吐量本身是Server級別多CPU機器上的默認GC方式,主要適合后臺運算不需要太多交互的任務

        收集器收集對象和算法收集器類型說明適用場景
        Serial Old老年代,標記整理算法單線程
        Client模式下虛擬機使用
        Parallel Old老年代,標記整理算法并行的多線程收集器Paraller Scavenge收集器的老年代版本,為了配置Parallel Svavenge的面向吞吐量的特性而開發(fā)的對應組合在注重吞吐量以及CPU資源敏感的場合采用
        CMS老年代,標記清除算法并行與并發(fā)收集器盡可能的縮短垃圾收集時用戶線程停止時間;缺點在于,1.內存碎片,2.需要更多CPU資源,3.浮動垃圾問題,需要更大的堆空間重視服務的相應速度,系統(tǒng)停頓時間和用戶體驗的互聯網網站或者B/S系統(tǒng)?;ヂ摼W后端目前cms是主流的垃圾回收器
        G1跨新生代和老年代;標記整理+化整為零
        并行與并發(fā)收集器JDK1.7才正式引入,采用分區(qū)回收的思維,基本不犧牲吞吐量的前提下完成低停頓的內存回收;可預測的停頓是其最大的優(yōu)勢
        瀏覽 44
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. AⅤ天堂 | 三区麻豆传媒视频 | 国产精品色视频 | 欧美小逼 | 黄色在线看网站 |