1. 程式人生 > >GC回收演算法&&GC回收器

GC回收演算法&&GC回收器

GC回收演算法


什麼是垃圾?

類比日常生活中,如果一個東西經常沒被使用,那麼就可以說是垃圾。
同理,如果一個物件不可能再被引用,那麼這個物件就是垃圾,應該被回收。

垃圾:不可能再被引用的物件。


finalize方法

  • 在物件沒有被引用時呼叫
  • 在Object類裡定義

    新生代與老年代

IBM公司的研究表明,在新生代中的物件 98% 是朝生夕死的。

在實際的 JVM 新生代劃分中,不是採用等分為兩塊記憶體的形式。而是分為:Eden 區域、Survivorfrom 區域、Survivorto 區域 這三個區域。

所以在HotSpot虛擬機器中,JVM 將記憶體劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,其大小佔比是8:1:1。當回收時,將Eden和Survivofrom中還存活的物件一次性複製到Survivorto空間上,最後清理掉Survivorfrom和剛才用過的Eden空間。

新生代一般佔據堆的1/3空間,老年代佔據2/3。


判斷物件是否存活

引用計數法

在一個物件被引用時加一,被去除引用時減一,這樣我們就可以通過判斷引用計數是否為零來判斷一個物件是否為垃圾。這種方法我們一般稱之為「引用計數法」。主流的Java虛擬機器裡面都沒有選用引用計數演算法來管理記憶體

  • 什麼是迴圈引用?(環)

    A 引用了 B,B 引用了 C,C 引用了 A,它們各自的引用計數都為 1。但是它們三個物件卻從未被其他物件引用,(假設有1000個物件時,這三個就是垃圾;如果只有4個物件,那麼另外一個就是垃圾)只有它們自身互相引用。從垃圾的判斷思想來看,它們三個確實是不被其他物件引用的,但是此時它們的引用計數卻不為零。


可達性分析演算法

通過一系列名為”GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈相連時,則證明此物件是不可用的。

GC Roots:

  • 虛擬機器棧中引用的物件
  • 方法區靜態屬性引用的物件
  • 方法區常量引用的物件
  • JNI引用的物件(Native方法)

根搜尋演算法:一種通過遍歷的方式判斷物件是否可達的垃圾標記演算法。


垃圾回收演算法


垃圾回收——標記清除演算法(適用老年代)

它將垃圾回收分為兩個階段:標記階段和清除階段。

在標記階段,標記所有從根節點出發的可達物件。因此,所有未被標記的物件就是未被引用的垃圾物件。

在清除階段,清除所有未被標記的物件。

問題:產生空間碎片。


垃圾回收——複製演算法(適合年輕代)

將記憶體分為兩部分,每次只使用其中一部分。在垃圾回收時,將正在使用的記憶體中的存活物件複製到未使用的記憶體塊中,之後清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收。

問題:不會產生空間碎片,但內存摺半


垃圾回收——標記整理演算法(適合老年代。)


對比於標記清除演算法,在清除階段,它會將所有的存活物件移動到記憶體的另一端。之後清理邊界之外的所有空間。

這種演算法既避免了碎片的產生,又不需要兩塊相同的記憶體空間,因此價效比較高。


“因地制宜”——分代演算法

分代演算法,就是根據 JVM 記憶體的不同記憶體區域,採用不同的垃圾回收演算法。

例如對於存活物件少的新生代區域,比較適合採用複製演算法。這樣只需要複製少量物件,便可完成垃圾回收,並且還不會有記憶體碎片。

而對於老年代這種存活物件多的區域,比較適合採用標記壓縮演算法或標記清除演算法,這樣不需要移動太多的記憶體物件。


GC回收器


Serial 回收器

  • 單執行緒序列回收
  • 使用複製演算法
  • 會產生較長時間的停頓(Stop the world)
  • 不會產生執行緒切換的開銷

通過JVM引數-XX:+UseSerialGC可以使用序列垃圾回收器。


ParNew回收器

  • 多執行緒並行回收
  • 新生代回收器,採用複製演算法

引數控制:-XX:+UseParNewGC


Parallel Scavenge回收器

  • 多執行緒並行回收
  • 新生代回收器,採用複製演算法
  • 追求高吞吐量,充分利用CPU資源【吞吐量優先】

開啟引數:-XX:+UseParallelGC

GC自適應調節策略:Parallel Scavenge收集器可設定-XX:+UseAdptiveSizePolicy引數

當開關開啟時不需要手動指定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRation)、晉升老年代的物件年齡(-XX:PretenureSizeThreshold)等,虛擬機器會根據系統的執行狀況收集效能監控資訊,動態設定這些引數以提供最優的停頓時間和最高的吞吐量,這種調節方式稱為GC的自適應調節策略。

Serial Old 回收器

  • 老年代單執行緒回收
  • 使用標記整理演算法

Parallel Old回收器

  • 老年代多執行緒回收
  • 使用標記整理演算法


序列與並行的效率分析:

以新生登記為例,假設新生人數較多,數量在5000,使用序列的方式,可以理解為一個人複製5000的登記工作;

效率可想而知。

使用並行的方式可以理解為有100個人負責登記,效率就會顯著提升。

但如果新生只有50個人,一個人登記就綽綽有餘了。


新生的數量可以理解為GC回收物件的數量,而負責登記的人就是CPU的核心計算數量。

對於新生代,回收次數頻繁,使用並行方式高效。

對於老年代,回收次數少,使用序列方式節省資源。(CPU並行需要切換執行緒,序列可以省去切換執行緒的資源)




CMS回收器

  • 併發低停頓收集器
  • 使用標記清除演算法
  • 四個階段
    • 初始標記 (標記GC Roots可以直接關聯的物件,速度很快)
    • 併發標記 (進行GC Roots Tracing,判斷物件是否存活)
    • 重新標記 (校準併發標記物件的存活狀態)
    • 併發清除 (回收標記的物件)
  • 初始標記和重新標記仍然需要Stop The World
  • CMS缺點
    • 由於併發帶來的CPU資源消耗
    • 由於併發收集在回收過程中產生的浮動垃圾無法清除
    • 使用標記清除演算法帶來的空間碎片問題

G1回收器


  • 使用於JDK1.7。
  • 使用分代垃圾回收策略。
  • 新特性:使用分割槽演算法。使記憶體不再連續。
  • 支援很大的堆,高吞吐量。

通過JVM引數 -XX:+UseG1GC 使用G1垃圾回收器


G1特點:

  • 並行與併發:並行體現在G1可以利用CPU的多個核心,縮短stop the world時間;併發體現在某些收集器和Java執行緒可以同時執行。

  • 分代收集

  • 空間整合,G1收集器採用標記整理演算法,不會產生記憶體空間碎片。分配大物件時不會因為無法找到連續空間而提前觸發下一次GC。

  • 能建立可預測的時間停頓模型,可以指定在M時間段內,垃圾回收時間不能超過N


並行與併發:

並行:同時處理多個任務。

併發:序列處理多個任務,但任務之間的切換很快,感覺上是並行執行。


並行是建立在多核CPU上的,多核指的是在一塊CPU上整合多個計算引擎。引擎之間可同時進行運算。

舉例:

單核運算時代就好比400米短跑,每跑完一個400米執行完一個任務。

多核運算時代可以理解為4*100接力,雖然單個任務的執行和單核一樣,但對於多個任務來說,單核是需要完成400的全部距離才能進行第二個任務。但多核只要第一個人跑完了100米,就可以開始第二個任務了。

並行處理多個任務的能力可以理解為核心之間的接力賽。


併發是指通過CPU在多個任務之間快速切換來達到同時執行的效果。


  • 分割槽演算法(G1記憶體結構)


在G1回收器之前,垃圾回收器分配的記憶體都是連續的。

在G1回收器中,垃圾回收器將記憶體分為大量區塊。

humongous:儲存巨型物件,當物件超過普通區塊的一半時,分配一個巨型區塊。


  • G1回收器工作步驟

  • 新生代 GC
  • 併發標記週期
  • 混合收集
  • 如果需要,可能進行 FullGC


    • 新生代GC

Eden區被佔滿,新生代GC啟動,回收Eden和Survivor。

注:survivor會被回收掉一部分,但回收後至少有一個survivor區存在。

為什麼???

新生代GC採用複製演算法,將Eden區中的存活物件複製到Survivor區中。


    • 併發標記週期
  • 初始標記:標記從根節點直接可達的物件。(會產生全域性停頓)
  • 根區域掃描:掃描Survivor區直接可達的老年代區域物件,並標記。(和應用程式併發,但不能和新生代GC同時執行【新生代GC有修改Survivor的操作】)
  • 併發標記:掃描並查詢整個堆記憶體存活物件,並標記。(可以被新生代GC打斷)
  • 再次標記:由於應用程式持續進行,需要修正標記結果。(會產生全域性停頓)
  • 獨佔清理:計算各個區域的存活物件和GC回收比例,並進行排序,識別可以混合回收的區域。為下階段做鋪墊。(有停頓)
  • 併發清理階段:識別並清理完全空閒的區域。


    • 混合回收

優先回收垃圾比例高的區域。(GC:Garbage First)

執行年輕代和老年代GC。

混合GC執行多次之後,會觸發新生代GC。然後迴圈:


GC的兩種觸發情況

  • Minor GC:新物件產生,申請Eden區失敗後會觸發Minor GC

  • Full GC:對整個堆的物件進行清理。

    • 觸發條件

      • System.gc()方法的呼叫

      • 老年代空間不足

      • 方法區空間不足

瞭解GC日誌

檢視JDK8預設使用哪種回收器

java -XX:+PrintCommandLineFlags -version

-XX:+UseParallelGC

使用Parallel Scavenge新生代回收器和Parallel Old老年代回收器


[GC (Allocation Failure) [PSYoungGen: 5986K->696K(8704K)] 5986K->704K(9216K), 0.0018526 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 4792K->696K(8704K)] 4800K->704K(9216K), 0.0031653 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 4945K->680K(8704K)] 4953K->688K(9216K), 0.0022002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 4776K->712K(8704K)] 4784K->720K(9216K), 0.0007493 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 4808K->648K(8704K)] 4816K->656K(9216K), 0.0008800 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 4744K->664K(8704K)] 4752K->672K(9216K), 0.0008349 secs] [Times: user=0.00 sys=0.02, real=0.00 secs] 
[GC (Allocation Failure) --[PSYoungGen: 4760K->4760K(8704K)] 4768K->5268K(9216K), 0.0022344 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 4760K->113K(8704K)] [ParOldGen: 508K->496K(512K)] 5268K->609K(9216K), [Metaspace: 3222K->3222K(1056768K)], 0.0069196 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [PSYoungGen: 4209K->192K(8704K)] 4705K->688K(9216K), 0.0007751 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 4288K->160K(8704K)] 4784K->656K(9216K), 0.0018608 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

Allocation Failure

表明本次引起GC的原因是因為在年輕代中沒有足夠的空間能夠儲存新的資料了。

PSYoungGen

新生代Eden和FromSpace,PS指Parallel Scavenge ,

PSOldGen

老年代

[PSYoungGen: 5986K->696K(8704K)] 5986K->704K(9216K)

中括號內:GC回收前年輕代堆大小,回收後大小,(年輕代堆總大小)

括號外:GC回收前年輕代和老年代大小,回收後大小,(年輕代和老年代總大小)

user代表使用者態回收耗時,sys核心態回收耗時,rea實際耗時