垃圾收集與幾種常用的垃圾收集演算法

來源:互聯網
上載者:User

標籤:複製演算法   時間   介紹   count   虛引用   關注   優先   關係   組合   

 前言:

  首先思考垃圾收集(Garbage Collection,GC)需要完成的三件事情

  1)哪些記憶體需要回收?

  2)什麼時候回收?

  3)如何回收?

  再上一個部落格中提到了Java記憶體運行時地區的各個部分,其中程式計數器、虛擬機器棧、本地方法棧3個地區會隨著線程而生,隨線程而滅;棧中的棧幀隨著方法的進行有條不紊地執行著出棧和入棧操作。每一個棧幀中分配多少記憶體基本上是在類結構確定下來時就已知得,因此這幾個地區的記憶體配置回收都具備確定性,在這幾個地區就不需要過多的考慮回收的問題,因為在方法結束或線程結束時記憶體就被回收了。而Java堆和方法區則不一樣,一個介面中的實作類別需要的記憶體可能不一樣,一個方法中的多個分支需要的記憶體也可能不一樣,我們只有在程式處於運行期間才能知道會建立那些對象,這部分記憶體的分配和回收都是動態,垃圾收集器所關注的是這部分記憶體。

 一、對象已死

  1.所謂對象已死,其實就是垃圾收集器在對堆進行回收之前需要判斷這些對象中哪些還“存活著”,哪些已經“死去”

  2.判斷演算法

    1)引用計數法(Reference Counting):許多教科書上判斷對象是否存活都是這個演算法,但是在主流的Java虛擬機器裡沒有選用這個演算法來管理記憶體,下面來簡單介紹一下此演算法,其實就是為對象中添加一個引用計數器,每當一個地方引用它時,計數器就加1;當引用失效時,計數器就減1;任何時刻計數器為0的對象就是不可能再被使用的

    2)可達性分析演算法(Reachability Analysis):在主流的商用程式語言的實現中,都是通過可達性分析法來判定對象是否存活的。這個演算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜尋,搜尋所走的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是停用

    

    在Java語言中,可作為GC Roots的對象包括:

      虛擬機器棧(棧幀中的本地變數表)中引用的對象

      方法區中常量引用的對象

      方法區中類靜態屬性引用的對象

      本地方法棧中JNI(即一般說的Native方法)引用的對象

  3.引用

    其實無論通過那種演算法來判斷對象是否已死,判斷都與“引用”有關

    Java中引用可分為強引用、軟引用、弱引用、虛引用四種,這4中引用強度一次降低

      1)強引用:程式間普遍存在的,類似“Object obj = new Object()”這類引用,只要強引用還存在,垃圾收集器就永遠不會回收掉被引用的對象

      2)軟引用:用來描述一些還有用但並非必須的對象。對於軟引用關聯的對象,在系統將要發生記憶體溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會拋出記憶體溢出異常

      3)弱引用:也是用來描述非必須對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉被弱引用關聯的對象

      4)虛引用:也成為幽靈引用或者歡迎引用,它是最弱的一種參考關聯性。一個對象是否具有虛引用的存在,完全不會對其存留時間構成影響,也無法通過虛引用來取得一個對象執行個體

 二、垃圾收集演算法

  1.標記-清除演算法

    最基本的收集演算法“標記-清除”(Mark-Sweep)演算法,演算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成後統一回收所有被標記的對象,之所以說它是最基本的收集演算法,是因為後續的收集演算法都是基於這種思路並對其不足進行改進而得到的。它的主要不足有兩個:

    一是效率問題,標記和清除效率都不高,二是空間問題,標記清除後會產生大量不連續的記憶體片段,空間片段太多可能會導致以後程式在運行過程中需要分配較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作

    執行過程如:

    

  2.複製演算法

    為瞭解決效率問題,一種稱為“複製”(Copying)的收集演算法出現了,他將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這塊的記憶體用完了,就將還存活這的對象複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。這樣使得每次都是對整個半區進行記憶體回收,記憶體配置時也就不用考慮記憶體片段等複雜情況,只要移動堆頂指標,按順序分配記憶體即可,實現簡單,運行高效。只是這種演算法的代價是將記憶體縮小為了原來的一半,未免太高了一點。

    

 

  3.標記-整理演算法

    複製收集演算法在對象存活率較高時就要進行較多的複製操作,效率將會變低。更關鍵的是如果不想浪費50%的空間就要使用額外的空間進行分配擔保(Handle Promotion當空間不夠時,需要依賴其他記憶體),以應對被使用的記憶體中所有對象都100%存活的極端情況

    對於“標記-整理”演算法,標記過程仍與“標記-清除”演算法一樣,但是後續步驟不是直接對可回收對象進行清理,而是讓所有的存活對象都向一端移動,然後直接清理掉端邊界以外的記憶體,”標記-整理“演算法如下:

    

  4.分代收集演算法

    當前的商業虛擬機器的垃圾收集都是採用“分代收集”(Generational Collection)演算法,這種演算法並沒有什麼新的思想,只是根據對象存活周期的不同將記憶體劃分為幾塊。一般是把堆劃分為新生代和老年代,這樣就可以根據各個年代的特點採用最適合的收集演算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就採用複製演算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或者“標記-整理”演算法來進行回收

 三、記憶體配置與回收策略

  對象的記憶體配置往大方向上講,就是在堆上分配,對象主要分配在新生代的Eden區上(一種分代布局方式,將新生代記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間,這裡就不細說了,瞭解就好),如果啟動了本地線程分配緩衝TLAB(Thread Local Allocation Buffer),將按線程優先在TLAB上分配。少數情況下也可能會直接分配在老年代中,分配的規則並不是百分之百固定的,其細節取決於當前使用的是哪一種垃圾收集器組合,還有虛擬機器中與記憶體相關的參數設定

  1.對象優先在Eden分配

    大多數情況下,對象在新生代Eden區中分配。當Eden區沒有足夠的空間進行分配時,虛擬機器將發起一次新生代記憶體回收(Minor GC:發生在新生代的垃圾收集動作,因為Java對象大多數都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。這裡也順帶說一下老年代記憶體回收Major GC:經常會伴隨至少一次Minor GC,Major GC的速度一般會比Minor GC慢10倍以上)

  2.大對象直接進入老年代

    所謂大對象,是指需要大量連續記憶體空間的Java對象,最典型的大對象就是那種很長的字串和數組。大對象對虛擬機器的記憶體配置來說就是一個壞訊息,經常出現大對象容易導致記憶體還有不少空間時就提前觸發垃圾收集以擷取足夠的連續空間來安置他們

  3.長期存活的對象將進入老年代

    既然Java虛擬機器採用了分代收集的思想來管理記憶體,那麼記憶體回收時就必須能識別哪些對象應該放在新生代,哪些對象應放在老年代。為了做到這一點,虛擬機器為每個對象定義了一個對象年齡計數器。如果對象在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且對象年齡增加1歲,當它的年齡增加到一定程度(預設15歲),將會晉陞到老年代中

  4.動態對象年齡判定

    為了能更好地適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求對象的年齡必須達到了最大年齡才能晉陞老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到最大年齡

  5.空間分配擔保

    在發生Minor GC之前,虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果這個條件成立,那麼Minor GC可以確保是安全的。如果不成立虛擬機器會查看HandlePromotionFailure設定值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於曆次晉陞到老年代對象的平均大小,如果大於,將嘗試著進行一次Minor GC,儘管這次Minor GC有風險;如果小於,或者不允許擔保失敗,那這是也要改為一次老年代記憶體回收

    解釋一下“風險”是什麼風險:新生代使用的是複製收集演算法,但為了記憶體利用率,只使用其中一個Survivor空間作為輪換備份,因此當出現大量對象在Minor GC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中對象都存活),就需要老年代進行分配擔保,把Survivor無法容納的對象直接進入老年代   

補充: 

  參考:深入Java虛擬機器

  對垃圾收集部分與記憶體配置部分做了簡單整理,供大家快速瞭解此部分知識點,想要深入瞭解可以去看此書

垃圾收集與幾種常用的垃圾收集演算法

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.