今天吃完飯,吃撐了,也不想寫代碼,就寫着寫一篇博客。就來寫一個jvm垃圾收集器相關的吧
java
JVM規範對於垃圾收集器的應該如何實現沒有任何規定,所以不一樣的廠商、不一樣版本的虛擬機所提供的垃圾收集器差異較大,這裏只看HotSpot虛擬機。 就像沒有最好的算法同樣,垃圾收集器也沒有最好,只有最合適。咱們能作的就是根據具體的應用場景選擇最合適的垃圾收集器。算法
Serial(串行)收集器收集器是最基本、歷史最悠久的垃圾收集器了(新生代採用複製算法,老生代採用標誌整理算法)。你們看名字就知道這個收集器是一個單線程收集器了。 它的 「單線程」 的意義不只僅意味着它只會使用一條垃圾收集線程去完成垃圾收集工做,更重要的是它在進行垃圾收集工做的時候必須暫停其餘全部的工做線程( "Stop The World" :將用戶正常工做的線程所有暫停掉),直到它收集結束。 看圖理解:瀏覽器
上圖中:服務器
當它進行GC工做的時候,雖然會形成Stop-The-World,正如每種算法都有存在的緣由,該串行收集器也有存在的緣由:由於簡單而高效(與其餘收集器的單線程比),對於限定單個CPU的環境來講,沒有線程交互的開銷,專心作GC,天然能夠得到最高的單線程效率。因此Serial收集器對於運行在client模式下的應用是一個很好的選擇(到目前爲止,它依然是虛擬機運行在client模式下的默認新生代收集器) 串行收集器的缺點很明顯,虛擬機的開發者固然也是知道這個缺點的,因此一直都在縮減Stop The World的時間。 在後續的垃圾收集器設計中停頓時間在不斷縮短(可是仍然還有停頓,尋找最優秀的垃圾收集器的過程仍然在繼續)多線程
添加該參數來顯式的使用串行垃圾收集器: "-XX:+UseSerialGC"閉包
ParNew收集器其實就是Serial收集器的多線程版本,除了使用多線程進行垃圾收集外,其他行爲(控制參數、收集算法、回收策略等等)和Serial收集器徹底同樣。 它是許多運行在Server模式下的虛擬機的首要選擇,除了Serial收集器外,目前只有它能與CMS收集器配合工做。 CMS收集器是一個被認爲具備劃時代意義的併發收集器,所以若是有一個垃圾收集器能和它一塊兒搭配使用讓其更加完美,那這個收集器必然也是一個不可或缺的部分了。 收集器的運行過程以下圖所示: 併發
在Server模式下,ParNew收集器是一個很是重要的收集器,由於除Serial外,目前只有它能與CMS收集器配合工做; 但在單個CPU環境中,不會比Serail收集器有更好的效果,由於存在線程交互開銷。框架
指定使用CMS後,會默認使用ParNew做爲新生代收集: "-XX:+UseConcMarkSweepGC" 強制指定使用ParNew:
"-XX:+UseParNewGC" 指定垃圾收集的線程數量,ParNew默認開啓的收集線程與CPU的數量相: "-XX:ParallelGCThreads"jvm
Parallel Scavenge收集器是一個新生代收集器,它也是使用複製算法的收集器,又是並行的多線程收集器。 Parallel Scavenge收集器關注點是吞吐量(如何高效率的利用CPU)。 CMS等垃圾收集器的關注點更多的是用戶線程的停頓時間(提升用戶體驗)。 所謂吞吐量就是CPU中用於運行用戶代碼的時間與CPU總消耗時間的比值。(吞吐量:CPU用於用戶代碼的時間/CPU總消耗時間的比值,即=運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間)。好比,虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。) 運行示意圖: 佈局
Parallel Scavenge收集器提供了不少參數供用戶找到最合適的停頓時間或最大吞吐量,若是對於收集器運做不太瞭解的話,不進行手工優化,能夠選擇把內存管理優化交給虛擬機去完成。
Parallel Scavenge收集器提供兩個參數用於精確控制吞吐量:
控制最大垃圾收集停頓時間 "-XX:MaxGCPauseMillis"
控制最大垃圾收集停頓時間,大於0的毫秒數; MaxGCPauseMillis設置得稍小,停頓時間可能會縮短,但也可能會使得吞吐量降低;由於可能致使垃圾收集發生得更頻繁; 設置垃圾收集時間佔總時間的比率 "-XX:GCTimeRatio"
設置垃圾收集時間佔總時間的比率,0 < n < 100的整數; GCTimeRatio至關於設置吞吐量大小; 垃圾收集執行時間佔應用程序執行時間的比例的計算方法是: 1 / (1 + n) 。 例如,選項-XX:GCTimeRatio=19,設置了垃圾收集時間佔總時間的5% = 1/(1+19);默認值是1% = 1/(1+99),即n=99; 垃圾收集所花費的時間是年輕一代和老年代收集的總時間; 若是沒有知足吞吐量目標,則增長代的內存大小以儘可能增長用戶程序運行的時間;
Serial收集器的老年代版本,它一樣是一個單線程收集器。 它主要有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用,另外一種用途是做爲CMS收集器的後備方案
Parallel Scavenge收集器的老年代版本。 使用多線程和「標記-整理」算法。在注重吞吐量以及CPU資源的場合,均可以優先考慮 Parallel Scavenge收集器和Parallel Old收集器。 在JDK1.6纔有的。
指定使用Parallel Old收集器: "-XX:+UseParallelOldGC"
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。它很是適合在注重用戶體驗的應用上使用。
從名字中的Mark Sweep這兩個詞能夠看出,CMS收集器是一種 「標記-清除」算法實現的,它的運做過程相比於前面幾種垃圾收集器來講更加複雜一些。整個過程可分爲四個步驟:
設置參數 指定使用CMS收集器 "-XX:+UseConcMarkSweepGC"
面向併發設計的程序都對CPU資源比較敏感(併發程序的特色)。在併發階段,它雖然不會致使用戶線程停頓,但會由於佔用了一部分線程(或者說CPU資源)而致使應用程序變慢,總吞吐量會下降。(在對帳系統中,不適合使用CMS收集器)。 CMS的默認收集線程數量是=(CPU數量+3)/4; 當CPU數量越多,回收的線程佔用CPU就少。 也就是當CPU在4個以上時,併發回收時垃圾收集線程很多於25%的CPU資源,對用戶程序影響可能較大;不足4個時,影響更大,可能沒法接受。(好比 CPU=2時,那麼就啓動一個線程回收,佔了50%的CPU資源。) (一個回收線程會在回收期間一直佔用CPU資源)
沒法處理浮動垃圾,可能出現"Concurrent Mode Failure"失敗 在併發清除時,用戶線程新產生的垃圾,稱爲浮動垃圾;
因爲CMS是基於「標記+清除」算法來回收老年代對象的,所以長時間運行後會產生大量的空間碎片問題,可能致使新生代對象晉升到老生代失敗。 因爲碎片過多,將會給大對象的分配帶來麻煩。所以會出現這樣的狀況,老年代還有不少剩餘的空間,可是找不到連續的空間來分配當前對象,這樣不得不提早觸發一次Full GC。
爲了解決空間碎片問題,CMS收集器提供−XX:+UseCMSCompactAlFullCollection標誌,使得CMS出現上面這種狀況時不進行Full GC,而開啓內存碎片的合併整理過程; 但合併整理過程沒法併發,停頓時間會變長; 默認開啓(但不會進行,須要結合CMSFullGCsBeforeCompaction使用);
整體來看,CMS與Parallel Old垃圾收集器相比,CMS減小了執行老年代垃圾收集時應用暫停的時間; 但卻增長了新生代垃圾收集時應用暫停的時間、下降了吞吐量並且須要佔用更大的堆空間; (緣由:CMS不進行內存空間整理節省了時間,可是可用空間再也不是連續的了,垃圾收集也不能簡單的使用指針指向下一次可用來爲對象分配內存的地址了。相反,這種狀況下,須要使用可用空間列表。即,會建立一個指向未分配區域的列表,每次爲對象分配內存時,會從列表中找到一個合適大小的內存區域來爲新對象分配內存。這樣作的結果是,老年代上的內存的分配比簡單實用碰撞指針分配內存消耗大。這也會增長年輕代垃圾收集的額外負擔,由於老年代中的大部分對象是在新生代垃圾收集的時候重新生代提高爲老年代的。) 當新生代對象沒法分配過大對象,就會放到老年代進行分配。
上一代的垃圾收集器(串行serial, 並行parallel, 以及CMS)都把堆內存劃分爲固定大小的三個部分: 年輕代(young generation), 年老代(old generation), 以及持久代(permanent generation)。
G1(Garbage-First)是JDK7-u4才推出商用的收集器;
G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器。以極高機率知足GC停頓時間要求的同時,還具有高吞吐量性能特徵。被視爲JDK1.7中HotSpot虛擬機的一個重要進化特徵。
G1的使命是在將來替換CMS,而且在JDK1.9已經成爲默認的收集器。
G1能充分利用CPU、多核環境下的硬件優點,使用多個CPU(CPU或者CPU核心)來縮短stop-The-World停頓時間。部分其餘收集器本來須要停頓Java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓java程序繼續執行。
雖然G1能夠不須要其餘收集器配合就能獨立管理整個GC堆,可是仍是保留了分代的概念。
與CMS的「標記--清理」算法不一樣,G1從總體來看是基於「標記整理」算法實現的收集器;從局部上來看是基於「複製」算法實現的。
(火車算法是分代收集器所用的算法,目的是在成熟對象空間中提供限定時間的漸進收集。在後面一篇中會專門介紹)
這是G1相對於CMS的另外一個大優點,下降停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能創建可預測的停頓時間模型。能夠明確指定M毫秒時間片內,垃圾收集消耗的時間不超過N毫秒。在低停頓的同時實現高吞吐量。
能夠有計劃地避免在Java堆的進行全區域的垃圾收集; G1收集器將內存分大小相等的獨立區域(Region),新生代和老年代概念保留,可是已經再也不物理隔離。 G1跟蹤各個Region得到其收集價值大小,在後臺維護一個優先列表; 每次根據容許的收集時間,優先回收價值最大的Region(名稱Garbage-First的由來); 這就保證了在有限的時間內能夠獲取儘量高的收集效率;
一個Region不多是孤立的,一個Region中的對象可能被其餘任意Region中對象引用,判斷對象存活時,是否須要掃描整個Java堆才能保證準確? 在其餘的分代收集器,也存在這樣的問題(而G1更突出):回收新生代也不得不一樣時掃描老年代? 這樣的話會下降Minor GC的效率;
不管G1仍是其餘分代收集器,JVM都是使用Remembered Set來避免全局掃描: 每一個Region都有一個對應的Remembered Set; 每次Reference類型數據寫操做時,都會產生一個Write Barrier暫時中斷操做; 而後檢查將要寫入的引用指向的對象是否和該Reference類型數據在不一樣的Region(其餘收集器:檢查老年代對象是否引用了新生代對象); 若是不一樣,經過CardTable把相關引用信息記錄到引用指向對象的所在Region對應的Remembered Set中; 當進行垃圾收集時,在GC根節點的枚舉範圍加入Remembered Set; 就能夠保證不進行全局掃描,也不會有遺漏。
具體什麼狀況下應用G1垃圾收集器比CMS好,能夠參考如下幾點(但不是絕對): 超過50%的Java堆被活動數據佔用; 對象分配頻率或年代的提高頻率變化很大; GC停頓時間過長(長於0.5至1秒); 建議: 若是如今採用的收集器沒有出現問題,不用急着去選擇G1; 若是應用程序追求低停頓,能夠嘗試選擇G1; 是否代替CMS只有須要實際場景測試才知道。(若是使用G1後發現性能尚未使用CMS好,那麼仍是選擇CMS比較好)
能夠經過下面的參數,來設置一些G1相關的配置。 指定使用G1收集器: "-XX:+UseG1GC"
當整個Java堆的佔用率達到參數值時,開始併發標記階段;默認爲45: "-XX:InitiatingHeapOccupancyPercent"
爲G1設置暫停時間目標,默認值爲200毫秒: "-XX:MaxGCPauseMillis"
設置每一個Region大小,範圍1MB到32MB;目標是在最小Java堆時能夠擁有約2048個Region: "-XX:G1HeapRegionSize"
新生代最小值,默認值5%: "-XX:G1NewSizePercent"
新生代最大值,默認值60%: "-XX:G1MaxNewSizePercent"
設置STW期間,並行GC線程數: "-XX:ParallelGCThreads"
設置併發標記階段,並行執行的線程數: "-XX:ConcGCThreads"
G1在標記過程當中,每一個區域的對象活性都被計算,在回收時候,就能夠根據用戶設置的停頓時間,選擇活性較低的區域收集,這樣既能保證垃圾回收,又能保證停頓時間,並且也不會下降太多的吞吐量。Remark(從新標記)階段新算法的運用,以及收集過程當中的壓縮,都彌補了CMS不足。 引用Oracle官網的一句話:「G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS)」。 G1計劃做爲併發標記-清除收集器(CMS)的長期替代品