天天看點

Java垃圾回收器

Java垃圾回收器
           

先來預覽一下Java各個版本的HotSpot JVM收集器:

Java垃圾回收器

注:圖檔上面為新生代收集器,下面為老年代收集器,連線說明可以搭配使用。

Serial(串行GC)收集器

Serial收集器是一個新生代收集器,單線程執行,使用複制算法。它在進行垃圾收集時,必須暫停其他所有的工作線程(使用者線程)。是Jvm client模式下預設的新生代收集器。對于限定單個CPU的環境來說,Serial收集器由于沒有線程互動的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。

ParNew(并行GC)收集器

ParNew收集器其實就是serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其餘行為與Serial收集器一樣。

Parallel Scavenge(并行回收GC)收集器

Parallel Scavenge收集器也是一個新生代收集器,它也是使用複制算法的收集器,又是并行多線程收集器。parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是盡可能地縮短垃圾收集時使用者線程的停頓時間,而parallel Scavenge收集器的目标則是達到一個可控制的吞吐量。吞吐量= 程式運作時間/(程式運作時間 + 垃圾收集時間),虛拟機總共運作了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。

Serial Old(串行GC)收集器

Serial Old是Serial收集器的老年代版本,它同樣使用一個單線程執行收集,使用“标記-整理”算法。主要使用在Client模式下的虛拟機。

Parallel Old(并行GC)收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“标記-整理”算法。

CMS(并發GC)收集器

CMS(Concurrent Mark Sweep)收集器是一種以擷取最短回收停頓時間為目标的收集器。CMS收集器是基于“标記-清除”算法實作的,整個收集過程大緻分為4個步驟:

①.初始标記(CMS initial mark)

②.并發标記(CMS concurrenr mark)

③.重新标記(CMS remark)

④.并發清除(CMS concurrent sweep)

其中初始标記、重新标記這兩個步驟任然需要停頓其他使用者線程。初始标記僅僅隻是标記出GC ROOTS能直接關聯到的對象,速度很快,并發标記階段是進行GC ROOTS 根搜尋算法階段,會判定對象是否存活。而重新标記階段則是為了修正并發标記期間,因使用者程式繼續運作而導緻标記産生變動的那一部分對象的标記記錄,這個階段的停頓時間會被初始标記階段稍長,但比并發标記階段要短。

由于整個過程中耗時最長的并發标記和并發清除過程中,收集器線程都可以與使用者線程一起工作,是以整體來說,CMS收集器的記憶體回收過程是與使用者線程一起并發執行的。

CMS收集器的優點:并發收集、低停頓,但是CMS還遠遠達不到完美,器主要有三個顯著缺點:

CMS收集器對CPU資源非常敏感。在并發階段,雖然不會導緻使用者線程停頓,但是會占用CPU資源而導緻引用程式變慢,總吞吐量下降。CMS預設啟動的回收線程數是:(CPU數量+3) / 4。

CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗後而導緻另一次Full GC的産生。由于CMS并發清理階段使用者線程還在運作,伴随程式的運作自熱會有新的垃圾不斷産生,這一部分垃圾出現在标記過程之後,CMS無法在本次收集中處理它們,隻好留待下一次GC時将其清理掉。這一部分垃圾稱為“浮動垃圾”。也是由于在垃圾收集階段使用者線程還需要運作,

即需要預留足夠的記憶體空間給使用者線程使用,是以CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分記憶體空間提供并發收集時的程式運作使用。在預設設定下,CMS收集器在老年代使用了68%的空間時就會被激活,也可以通過參數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以降低記憶體回收次數提高性能。要是CMS運作期間預留的記憶體無法滿足程式其他線程需要,就會出現“Concurrent Mode Failure”失敗,這時候虛拟機将啟動後備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。是以說參數-XX:CMSInitiatingOccupancyFraction設定的過高将會很容易導緻“Concurrent Mode Failure”失敗,性能反而降低。

最後一個缺點,CMS是基于“标記-清除”算法實作的收集器,使用“标記-清除”算法收集後,會産生大量碎片。空間碎片太多時,将會給對象配置設定帶來很多麻煩,比如說大對象,記憶體空間找不到連續的空間來配置設定不得不提前觸發一次Full GC。為了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用于在Full GC之後增加一個碎片整理過程,還可通過-XX:CMSFullGCBeforeCompaction參數設定執行多少次不壓縮的Full GC之後,跟着來一次碎片整理過程。

G1收集器

G1(Garbage First)收集器是JDK1.7提供的一個新收集器,G1收集器基于“标記-整理”算法實作,也就是說不會産生記憶體碎片。還有一個特點之前的收集器進行收集的範圍都是整個新生代或老年代,而G1将整個Java堆(包括新生代,老年代)

一、什麼是Java垃圾回收器

Java垃圾回收器是Java虛拟機(JVM)的三個重要子產品(另外兩個是解釋器和多線程機制)之一,為應用程式提供記憶體的自動配置設定(Memory Allocation)、自動回收(Garbage Collect)功能,這兩個操作都發生在Java堆上(一段記憶體快)。某一個時點,一個對象如果有一個以上的引用(Rreference)指向它,那麼該對象就為活着的(Live),否則死亡(Dead),視為垃圾,可被垃圾回收器回收再利用。垃圾回收操作需要消耗CPU、線程、時間等資源,是以容易了解的是垃圾回收操作不是實時的發生(對象死亡馬上釋放),當記憶體消耗完或者是達到某一個名額(Threshold,使用記憶體占總記憶體的比列,比如0.75)時,觸發垃圾回收操作。有一個對象死亡的例外,java.lang.Thread類型的對象即使沒有引用,隻要線程還在運作,就不會被回收。

二、Java中的引用類型

從JDK1.2之後,Java對引用的概念進行了擴充,将引用分為強引用,軟引用,弱引用,虛引用,這四種引用的強度一次逐漸減弱

強引用(StrongReference)

強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛拟機甯願抛出OutOfMemoryError錯誤,使程式異常終止,也不會靠随意回收具有強引用的對象來解決記憶體不足的問題。ps:強引用其實也就是我們平時A a = new A()這個意思。

軟引用(SoftReference)

軟引用(soft reference)在強度上弱于強引用,通過類SoftReference來表示。它的作用是告訴垃圾回收器,程式中的哪些對象是不那麼重要,當記憶體不足的時候是可以被暫時回收的。當JVM中的記憶體不足的時候,垃圾回收器會釋放那些隻被軟引用所指向的對象。如果全部釋放完這些對象之後,記憶體還不足,才會抛出OutOfMemory錯誤。軟引用非常适合于建立緩存。當系統記憶體不足的時候,緩存中的内容是可以被釋放的。

弱引用(WeakReference)

在強度上弱于軟引用,通過類WeakReference來表示。它的作用是引用一個對象,但是并不阻止該對象被回收。如果使用一個強引用的話,隻要該引用存在,那麼被引用的對象是不能被回收的。弱引用則沒有這個問題。在垃圾回收器運作的時候,如果一個對象的所有引用都是弱引用的話,該對象會被回收。弱引用的作用在于解決強引用所帶來的對象之間在存活時間上的耦合關系。弱引用最常見的用處是在集合類中,尤其在哈希表中。哈希表的接口允許使用任何Java對象作為鍵來使用。當一個鍵值對被放入到哈希表中之後,哈希表對象本身就有了對這些鍵和值對象的引用。如果這種引用是強引用的話,那麼隻要哈希表對象本身還存活,其中所包含的鍵和值對象是不會被回收的。如果某個存活時間很長的哈希表中包含的鍵值對很多,最終就有可能消耗掉JVM中全部的記憶體。

幽靈引用(PhantomReference)

在介紹幽靈引用之前,要先介紹Java提供的對象終止化機制(finalization)。在Object類裡面有個finalize方法,其設計的初衷是在一個對象被真正回收之前,可以用來執行一些清理的工作。因為Java并沒有提供類似C++的析構函數一樣的機制,就通過 finalize方法來實作。但是問題在于垃圾回收器的運作時間是不固定的,是以這些清理工作的實際運作時間也是不能預知的。幽靈引用(phantom reference)可以解決這個問題。在建立幽靈引用PhantomReference的時候必須要指定一個引用隊列。當一個對象的finalize方法已經被調用了之後,這個對象的幽靈引用會被加入到隊列中。通過檢查該隊列裡面的内容就知道一個對象是不是已經準備要被回收了。

三、垃圾收集算法

  1. 标記-清除算法

    最基礎的收集算法是“标記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“标記”和“清除”兩個階段:首先标記出所有需要回收的對象,在标記完成後統一回收掉所有被标記的對象,它的标記過程其實在前一節講述對象标記判定時已經基本介紹過了。之是以說它是最基礎的收集算法,是因為後續的收集算法都是基于這種思路并對其缺點進行改進而得到的。它的主要缺點有兩個:一個是效率問題,标記和清除過程的效率都不高;另外一個是空間問題,标記清除之後會産生大量不連續的記憶體碎片,空間碎片太多可能會導緻,當程式在以後的運作過程中需要配置設定較大對象時無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。 标記-清除算法的執行過程如圖

    Java垃圾回收器
  2. 複制算法

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

    Java垃圾回收器
  3. 标記-整理算法

    複制收集算法在對象存活率較高時就要執行較多的複制操作,效率将會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行配置設定擔保,以應對被使用的記憶體中所有對象都100%存活的極端情況,是以在老年代一般不能直接選用這種算法。

    根據老年代的特點,有人提出了另外一種“标記-整理”(Mark-Compact)算法,标記過程仍然與“标記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體,“标記-整理”算法的示意圖如圖

    Java垃圾回收器
  4. 分代收集算法

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

四、Java虛拟機對堆的記憶體細化的幾個區域,并且這些區域都是采用哪些收集算法。

Java垃圾回收器

VM記憶體模型中分兩大塊,一塊是New Generation, 另一塊是Old Generation. 在New Generation中,有一個叫Eden的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to), 它們用來存放每次垃圾回收後存活下來的對象。在Old Generation中,主要存放應用程式中生命周期長的記憶體對象,還有個Permanent Generation,主要用來放JVM自己的反射對象,比如類對象和方法對象等。

新生代劃分為三個部分:分别為Eden、Survivor from、Survivor to,大小比例為8:1:1(為了防止複制收集算法的浪費記憶體過大)。每次隻使用Eden和其中的一塊Survivor,回收時将存活的對象複制到另一塊Survivor中,這樣就隻有10%的記憶體被浪費,但是如果存活的對象總大小超過了Survivor的大小,那麼就把多出的對象放入老年代中。

在三個區域中有兩個是Survivor區。對象在三個區域中的存活過程如下:

大多數新生對象都被配置設定在Eden區。

第一次GC過後Eden中還存活的對象被移到其中一個Survivor區。

再次GC過程中,Eden中還存活的對象會被移到之前已移入對象的Survivor區。

一旦該Survivor區域無空間可用時,還存活的對象會從目前Survivor區移到另一個空的Survivor區。而目前Survivor區就會再次置為空狀态。

經過數次(預設是15次,為什麼是15,因為HotSpot會在對象投中的标記字段裡記錄年齡,配置設定到的空間僅有4位,是以最多隻能記錄到15)在兩個Survivor區域移動後還存活的對象最後會被移動到老年代。

如上所述,兩個Survivor區域在任何時候必定有一個保持空白。如果同時有資料存在于兩個Survivor區或者兩個區域的的使用量都是0,則意味着你的系統可能出現了運作錯誤。

  1. 在New Generation塊中,垃圾回收一般用複制算法,速度快。每次GC的時候,存活下來的對象首先由Eden拷貝到某個Survivor Space, 當Survivor Space空間滿了後, 剩下的live對象就被直接拷貝到Old Generation中去。是以,每次GC後,Eden記憶體塊會被清空
  2. 在Old Generation塊中,垃圾回收一般用标記整理的算法,速度慢些,但減少記憶體要求.

垃圾回收分多級,0級為全部(Full)的垃圾回收,會回收Old段中的垃圾;1級或以上為部分垃圾回收,隻會回收New中的垃圾,記憶體溢出通常發生于Old段或Perm段垃圾回收後,仍然無記憶體空間容納新的Java對象的情況。

參考文章:http://www.cnblogs.com/mjorcen/p/3968018.html

http://blog.csdn.net/kimylrong/article/details/18265807

http://blog.csdn.net/jiangwei0910410003/article/details/40709457

https://segmentfault.com/a/1190000004926898#articleHeader21