JVM虛擬機垃圾回收(GC)算法及優缺點


一、什么是GC

  GC是jvm的垃圾回收,垃圾回收的規律和原則為:   次數上頻繁收集新生區(Young)   次數上較少收集養老區(Old)   基本上不動永久區(Perm)

二、GC算法(分代收集算法)

  GC總共有四大算法,分別是:
  ①引用計數法
  ②復制算法(Copying)
  ③標記清除(Mark-Sweep)
  ④標記壓縮(Mark-Compact)
  ⑤標記清除壓縮(Mark-Sweep-Compact)

1.1 引用計數法

1.2 復制算法(Copying)

  復制算法主要用在新生代中。

1.2.1 復制算法原理

  Minor GC會把Eden中的所有活的對象都移到Survivor區域中,如果Survivor區中放不下,那么剩下的活的對象就被移到Old generation中,也即一旦收集后,Eden是就變成空的了。

  當對象在 Eden ( 包括一個 Survivor 區域,這里假設是 from 區域 ) 出生后,在經過一次 Minor GC 后,如果對象還存活,並且能夠被另外一塊 Survivor 區域所容納( 上面已經假設為 from 區域,這里應為 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用復制算法將這些仍然還存活的對象復制到另外一塊 Survivor 區域 ( 即 to 區域 ) 中,然后清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),並且將這些對象的年齡設置為1,以后對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,通過-XX:MaxTenuringThreshold 來設定參數),這些對象就會成為老年代。

復制必交換,誰空誰為to

年輕代中的GC,主要是復制算法(Copying)

1.2.1 復制算法優缺點

  HotSpot JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from和to)。默認比例為8:1:1。
  一般情況下,新創建的對象都會被分配到Eden區(一些大對象特殊處理),這些對象經過第一次Minor GC后,如果仍然存活,將會被移到Survivor區。
  對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。因為年輕代中的對象基本都是朝生夕死的(90%以上),所以在年輕代的垃圾回收算法使用的是復制算法,復制算法的基本思想就是將內存分為兩塊,每次只用其中一塊,當這一塊內存用完,就將還活着的對象復制到另外一塊上面。復制算法不會產生內存碎片。

  復制算法它的缺點也是相當明顯的。

1、它浪費了一半的內存,這太要命了。

2、如果對象的存活率很高,我們可以極端一點,假設是100%存活,那么我們需要將所有對象都復制一遍,並將所有引用地址重置一遍。復制這一工作所花費的時間,在對象存活率達到一定程度時,將會變的不可忽視。 所以從以上描述不難看出,復制算法要想使用,最起碼對象的存活率要非常低才行,而且最重要的是,我們必須要克服50%內存的浪費。

1.3 標記清除(Mark-Sweep)算法

養老代一般是由標記清除或者是標記清除與標記整理的混合實現

1.3.1 標記清除算法原理

  當堆中的有效內存空間(available memory)被耗盡的時候,就會停止整個程序(也被稱為stop the world),然后進行兩項工作,第一項則是標記,第二項則是清除。

  • 標記:從引用根節點開始標記所有被引用的對象。標記的過程其實就是遍歷所有的GC Roots,然后將所有GC Roots可達的對象標記為存活的對象。

  • 清除:遍歷整個堆,把未標記的對象清除。

  缺點:此算法需要暫停整個應用,會產生內存碎片

  用通俗的話解釋一下標記/清除算法,就是當程序運行期間,若可以使用的內存被耗盡的時候,GC線程就會被觸發並將程序暫停,隨后將依舊存活的對象標記一遍,最終再將堆中所有沒被標記的對象全部清除掉,接下來便讓程序恢復運行。

1.3.2 標記清除算法優缺點:

  • 缺點

1、首先,它的缺點就是效率比較低(遞歸與全堆對象遍歷),而且在進行GC的時候,需要停止應用程序,這會導致用戶體驗非常差勁

2、其次,主要的缺點則是這種方式清理出來的空閑內存是不連續的(內存碎片),這點不難理解,我們的死亡對象都是隨即的出現在內存的各個角落的,現在把它們清除之后,內存的布局自然會亂七八糟。而為了應付這一點,JVM就不得不維持一個內存的空閑列表,這又是一種開銷。而且在分配數組對象的時候,尋找連續的內存空間會不太好找。

  • 優點
      不需要雙倍的內存空間。

1.4 標記壓縮(Mark-Compact)

養老代一般是由標記清除或者是標記清除與標記整理的混合實現

1.4.1 標記壓縮算法原理

  在整理壓縮階段,不再對標記的對象做回收,而是通過所有存活對象都向一端移動然后直接清除邊界以外的內存
  可以看到,標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當我們需要給新對象分配內存時,JVM只需要持有一個內存的起始地址即可,這比維護一個空閑列表顯然少了許多開銷。

1.4.2 標記清除算法優缺點

  • 優點
  **標記/整理算法不僅可以彌補標記/清除算法當中內存區域分散的缺點,也消除了復制算法當中,內存減半的高額代價**
  • 缺點

  標記/整理算法唯一的缺點就是效率也不高,不僅要標記所有存活對象,還要整理所有存活對象的引用地址。從效率上來說,標記/整理算法要低於復制算法。

1.5 標記清除壓縮(Mark-Sweep-Compact)

1.5.1 標記清除壓縮算法原理

  標記清除壓縮算法就是將標記清除算法與標記壓縮算法結合起來。

三、總結

3.1 各個算法對比

內存效率:復制算法>標記清除算法>標記整理算法(此處的效率只是簡單的對比時間復雜度,實際情況不一定如此)。

內存整齊度:復制算法=標記整理算法>標記清除算法。

內存利用率:標記整理算法=標記清除算法>復制算法。

  可以看出,效率上來說,復制算法是當之無愧的老大,但是卻浪費了太多內存,而為了盡量兼顧上面所提到的三個指標,標記/整理算法相對來說更平滑一些,但效率上依然不盡如人意,它比復制算法多了一個標記的階段,又比標記/清除多了一個整理內存的過程。

  所以針對不同的垃圾回收的算法的不同的特點,所以針對jvm堆中不同的區采用不同的算法,即分代收集算法

3.2 不同分區的不同算法

  • 年輕代(Young Gen)

  年輕代特點是區域相對老年代較小,對象存活率低。

  這種情況復制算法的回收整理,速度是最快的。復制算法的效率只和當前存活對象大小有關,因而很適用於年輕代的回收。而復制算法內存利用率不高的問題,通過hotspot中的兩個survivor的設計得到緩解。

  • 老年代(Tenure Gen)

  老年代的特點是區域較大,對象存活率高

  這種情況,存在大量存活率高的對象,復制算法明顯變得不合適。一般是由標記清除或者是標記清除與標記整理的混合實現。

Mark階段的開銷與存活對象的數量成正比,這點上說來,對於老年代,標記清除或者標記整理有一些不符,但可以通過多核/線程利用,對並發、並行的形式提標記效率。

Sweep階段的開銷與所管理區域的大小形狀相關,但Sweep“就地處決”的特點,回收的過程沒有對象的移動。使其相對其它有對象移動步驟的回收算法,仍然是效率最好的。但是需要解決內存碎片問題。

Compact階段的開銷與存活對象的數據成開比,如上一條所描述,對於大量對象的移動是很大開銷的,做為老年代的第一選擇並不合適。

  基於上面的考慮,老年代一般是由**標記清除**或者是**標記清除**與 **標記整理** 的混合實現。以hotspot中的CMS回收器為例,CMS是基於Mark-Sweep實現的,對於對象的回收效率很高,而對於碎片問題,CMS采用基於Mark-Compact算法的Serial Old回收器做為補償措施:當內存回收不佳(碎片導致的Concurrent Mode Failure時),將采用Serial Old執行Full GC以達到對老年代內存的整理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM