什麼時候回收垃圾?
首先筆者在其他部落格找到兩張圖
第一張顯示了分代後的記憶體空間圖

一個對象執行個體化時 先去看伊甸園有沒有足夠的空間
如果有 不進行垃圾回收 ,對象直接在伊甸園存儲.
如果伊甸園記憶體已滿,會進行一次minor gc
然後再進行判斷伊甸園中的記憶體是否足夠
如果不足 則去看存活區的記憶體是否足夠.
如果記憶體足夠,把伊甸園部分活躍對象儲存在存活區,然後把對象儲存在伊甸園.
如果記憶體不足,向老年代發送請求,查詢老年代的記憶體是否足夠
如果老年代記憶體足夠,将部分存活區的活躍對象存入老年代.然後把伊甸園的活躍對象放入存活區,對象依舊儲存在伊甸園.
如果老年代記憶體不足,會進行一次full gc,之後老年代會再進行判斷 記憶體是否足夠,如果足夠 同上.
如果不足 會抛出OutOfMemoryError.
GC雖然可以進行記憶體空間的釋放,但同時頻繁的GC一定會影響性能,GC發生的頻率越低,你的系統就越高效.
垃圾收集器
如圖為HotSpot虛拟機所有收集器及組合(連線)
(A)、圖中展示了7種不同分代的收集器:
Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
(B)、而它們所處區域,則表明其是屬于新生代收集器還是老年代收集器:
新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial Old、Parallel Old、CMS;
整堆收集器:G1;
(C)、兩個收集器間有連線,表明它們可以搭配使用:
Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1;
(D)、其中Serial Old作為CMS出現"Concurrent Mode Failure"失敗的後備預案;
1.Serial收集器
Serial(串行)垃圾收集器是最基本、發展曆史最悠久的收集器;
1、特點
針對新生代;
采用複制算法;
單線程收集;
進行垃圾收集時,必須暫停所有工作線程,直到完成;
即會"Stop The World";(JVM在背景自動發起和自動完成的,在使用者不可見的情況下,把使用者正常的工作線程全部停掉,即GC停頓;會帶給使用者不良的體驗)
2、應用場景
依然是HotSpot在Client模式下預設的新生代收集器;
也有優于其他收集器的地方:
簡單高效(與其他收集器的單線程相比);
對于限定單個CPU的環境來說,Serial收集器沒有線程互動(切換)開銷,可以獲得最高的單線程收集效率;
在使用者的桌面應用場景中,可用記憶體一般不大(幾十M至一兩百M),可以在較短時間内完成垃圾收集(幾十MS至一百多MS),隻要不頻繁發生,這是可以接受的
2.ParNew收集器
ParNew垃圾收集器是Serial收集器的多線程版本。
1、特點
除了多線程外,其餘的行為、特點和Serial收集器一樣;
如Serial收集器可用控制參數、收集算法、Stop The World、記憶體配置設定規則、回收政策等;
兩個收集器共用了不少代碼;
2、應用場景
在Server模式下,ParNew收集器是一個非常重要的收集器,因為除Serial外,目前隻有它能與CMS收集器配合工作;
但在單個CPU環境中,不會比Serail收集器有更好的效果,因為存線上程互動開銷。
3.Parallel Scavenge收集器
Parallel Scavenge垃圾收集器因為與吞吐量關系密切,也稱為吞吐量收集器(Throughput Collector)
1、特點
(A)、有一些特點與ParNew收集器相似
新生代收集器;
采用複制算法;
多線程收集;
(B)、主要特點是:它的關注點與其他收集器不同
CMS等收集器的關注點是盡可能地縮短垃圾收集時使用者線程的停頓時間;
而Parallel Scavenge收集器的目标則是達一個可控制的吞吐量(Throughput);
2、應用場景
高吞吐量為目标,即減少垃圾收集時間,讓使用者代碼獲得更長的運作時間;
當應用程式運作在具有多個CPU上,對暫停時間沒有特别高的要求時,即程式主要在背景進行計算,而不需要與使用者進行太多互動;
例如,那些執行批量處理、訂單處理、工資支付、科學計算的應用程式;
4.Serial Old收集器
Serial Old是 Serial收集器的老年代版本;
1、特點
針對老年代;
采用"标記-整理"算法(還有壓縮,Mark-Sweep-Compact);
單線程收集;
2、應用場景
主要用于Client模式;
而在Server模式有兩大用途:
(A)、在JDK1.5及之前,與Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);
(B)、作為CMS收集器的後備預案,在并發收集發生Concurrent Mode Failure時使用(後面詳解);
5.Parallel Old收集器
Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本; JDK1.6中才開始提供;
1、特點
針對老年代;
采用"标記-整理"算法;
多線程收集;
2、應用場景
JDK1.6及之後用來代替老年代的Serial Old收集器;
特别是在Server模式,多CPU的情況下;
這樣在注重吞吐量以及CPU資源敏感的場景,就有了Parallel Scavenge加Parallel Old收集器的"給力"應用組合;
6.CMS收集器
并發标記清理(Concurrent Mark Sweep,CMS)收集器也稱為并發低停頓收集器(Concurrent Low Pause Collector)或低延遲(low-latency)垃圾收集器;
1、特點
針對老年代;
基于"标記-清除"算法(不進行壓縮操作,産生記憶體碎片);
以擷取最短回收停頓時間為目标;
并發收集、低停頓;
需要更多的記憶體;
2、缺點
(A)、對CPU資源非常敏感
并發收集雖然不會暫停使用者線程,但因為占用一部分CPU資源,還是會導緻應用程式變慢,總吞吐量降低。
CMS的預設收集線程數量是=(CPU數量+3)/4;
當CPU數量多于4個,收集線程占用的CPU資源多于25%,對使用者程式影響可能較大;不足4個時,影響更大,可能無法接受。
(B)、無法處理浮動垃圾,可能出現"Concurrent Mode Failure"失敗
(1)、浮動垃圾(Floating Garbage)
在并發清除時,使用者線程新産生的垃圾,稱為浮動垃圾;
這使得并發清除時需要預留一定的記憶體空間,不能像其他收集器在老年代幾乎填滿再進行收集;
也要可以認為CMS所需要的空間比其他垃圾收集器大;
"-XX:CMSInitiatingOccupancyFraction":設定CMS預留記憶體空間;
JDK1.5預設值為68%;
JDK1.6變為大約92%;
(2)、"Concurrent Mode Failure"失敗
如果CMS預留記憶體空間無法滿足程式需要,就會出現一次"Concurrent Mode Failure"失敗;
這時JVM啟用後備預案:臨時啟用Serail Old收集器,而導緻另一次Full GC的産生;
這樣的代價是很大的,是以CMSInitiatingOccupancyFraction不能設定得太大。
C)、産生大量記憶體碎片
由于CMS基于"标記-清除"算法,清除後不進行壓縮操作;
前面《Java虛拟機垃圾回收(二) 垃圾回收算法》"标記-清除"算法介紹時曾說過:
産生大量不連續的記憶體碎片會導緻配置設定大記憶體對象時,無法找到足夠的連續記憶體,進而需要提前觸發另一次Full GC動作。
解決方法:
(1)、"-XX:+UseCMSCompactAtFullCollection"
使得CMS出現上面這種情況時不進行Full GC,而開啟記憶體碎片的合并整理過程;
但合并整理過程無法并發,停頓時間會變長;
預設開啟(但不會進行,結合下面的CMSFullGCsBeforeCompaction);
(2)、"-XX:+CMSFullGCsBeforeCompaction"
設定執行多少次不壓縮的Full GC後,來一次壓縮整理;
為減少合并整理過程的停頓時間;
預設為0,也就是說每次都執行Full GC,不會進行壓縮整理;
由于空間不再連續,CMS需要使用可用"空閑清單"記憶體配置設定方式,這比簡單實用"碰撞指針"配置設定記憶體消耗大;
3、CMS收集器運作過程
比前面幾種收集器更複雜,可以分為4個步驟:
(A)、初始标記(CMS initial mark)
僅标記一下GC Roots能直接關聯到的對象;
速度很快;
但需要"Stop The World";
(B)、并發标記(CMS concurrent mark)
進行GC Roots Tracing的過程;
剛才産生的集合中标記出存活對象;
應用程式也在運作;
并不能保證可以标記出所有的存活對象;
(C)、重新标記(CMS remark)
為了修正并發标記期間因使用者程式繼續運作而導緻标記變動的那一部分對象的标記記錄;
需要"Stop The World",且停頓時間比初始标記稍長,但遠比并發标記短;
采用多線程并行執行來提升效率;
(D)、并發清除(CMS concurrent sweep)
回收所有的垃圾對象;
7.G1收集器
G1(Garbage-First)是JDK7-u4才推出商用的收集器;
1、特點
(A)、并行與并發
能充分利用多CPU、多核環境下的硬體優勢;
可以并行來縮短"Stop The World"停頓時間;
也可以并發讓垃圾收集與使用者程式同時進行;
(B)、分代收集,收集範圍包括新生代和老年代
能獨立管理整個GC堆(新生代和老年代),而不需要與其他收集器搭配;
能夠采用不同方式處理不同時期的對象;
雖然保留分代概念,但Java堆的記憶體布局有很大差别;
将整個堆劃分為多個大小相等的獨立區域(Region);
新生代和老年代不再是實體隔離,它們都是一部分Region(不需要連續)的集合;
(C)、結合多種垃圾收集算法,空間整合,不産生碎片
從整體看,是基于标記-整理算法;
從局部(兩個Region間)看,是基于複制算法;
這是一種類似火車算法的實作;
都不會産生記憶體碎片,有利于長時間運作;
(D)、可預測的停頓:低停頓的同時實作高吞吐量
G1除了追求低停頓處,還能建立可預測的停頓時間模型;
可以明确指定M毫秒時間片内,垃圾收集消耗的時間不超過N毫秒;
2、應用場景
面向服務端應用,針對具有大記憶體、多處理器的機器;
最主要的應用是為需要低GC延遲,并具有大堆的應用程式提供解決方案;
如:在堆大小約6GB或更大時,可預測的暫停時間可以低于0.5秒;
用來替換掉JDK1.5中的CMS收集器;
在下面的情況時,使用G1可能比CMS好:
(1)、超過50%的Java堆被活動資料占用;
(2)、對象配置設定頻率或年代提升頻率變化很大;
(3)、GC停頓時間過長(長于0.5至1秒)。
從接來看如圖: