1.前言算法
垃圾收集器是前一章垃圾收集算法理論知識的具體實現了,不一樣虛擬機所提供的垃圾收集器可能會有很大差異,另外咱們必須提早說明一個道理:沒有最好的垃圾收集器,更加沒有萬能的收集器,只能選擇對具體應用最合適的收集器。這也是HotSpot爲何要實現這麼多收集器的緣由,下面咱們以HotSpot爲例講解。在寫以前,先介紹幾個概念。編程
1.1.並行和併發的區別多線程
這個區別以前在你專門的一節介紹過,這裏再重點提一下:這兩個名詞都是併發編程中的概念,在談論垃圾收集器的上下文語境中,能夠這麼理解這兩個名詞:併發
一、並行Parallel佈局
多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態。性能
二、併發Concurrent測試
指用戶線程與垃圾收集線程同時執行(但並不必定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上。優化
1.2.Minor GC和Full GC的區別spa
新生代GC(Minor GC):指發生在新生代的垃圾收集動做,由於Java對象大多都具有朝生夕滅的特性(存活率不高),因此Minor GC很是頻繁,通常回收速度也比較快。線程
老年代GC(Major GC / Full GC):指發生在老年代的垃圾收集動做,出現了Major GC,常常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略裏就有直接進行Major GC的策略選擇過程)。Major GC的速度通常會比Minor GC慢10倍以上。
1.3.吞吐量
吞吐量:就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 /(運行用戶代碼時間 + 垃圾收集時間)。虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
2.垃圾收集器組合
下面一張圖是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"失敗的後備預案(後面介紹);
2.1 Serial收集器
特性:
最基本、發展歷史最久的收集器,採用複製算法的單線程收集器,單線程一方面意味着它只會使用一個CPU或一條線程去完成垃圾收集工做,另外一方面也意味着在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束爲止,這個過程也稱爲 Stop The world。後者意味着,在用戶不可見的狀況下要把用戶正常工做的線程所有停掉,這顯然對不少應用是難以接受的。
應用場景:
Serial收集器依然是虛擬機運行在Client模式下的默認新生代收集器。 在用戶的桌面應用場景中,可用內存通常不大(幾十M至一兩百M),能夠在較短期內完成垃圾收集(幾十MS至一百多MS),只要不頻繁發生,這是能夠接受的
優點:
簡單而高效(與其餘收集器的單線程相比),對於限定單個CPU的環境來講,Serial收集器因爲沒有線程交互的開銷,專心作垃圾收集天然能夠得到最高的單線程收集效率。好比在用戶的桌面應用場景中,可用內存通常不大(幾十M至一兩百M),能夠在較短期內完成垃圾收集(幾十MS至一百多MS),只要不頻繁發生,這是能夠接受的。
Stop TheWorld 說明:
GC在後臺自動發起和自動完成的,在用戶不可見的狀況下,把用戶正常的工做線程所有停掉,即GC停頓,會帶給用戶不良的體驗;
從JDK1.3到如今,從Serial收集器-》Parallel收集器-》CMS-》G1,用戶線程停頓時間不斷縮短,但仍然沒法徹底消除;
設置參數
"-XX:+UseSerialGC":添加該參數來顯式的使用串行垃圾收集器;
Serial/Serial Old組合收集器運行示意圖以下:
2.2 ParNew收集器
ParNew收集器其實就是Serial收集器的多線程版本,除了使用多條線程進行垃圾收集外,其他行爲和Serial收集器徹底同樣,包括Serial收集器可用的全部控制參數、收集算法、Stop The world、對象分配規則、回收策略等都同樣。在實現上也共用了至關多的代碼。
應用場景:
ParNew收集器是許多運行在Server模式下的虛擬機中首選的新生代收集器。很重要的緣由是:除了Serial收集器以外,目前只有它能與CMS收集器配合工做(看圖)。在JDK1.5時期,HotSpot推出了一款幾乎能夠認爲具備劃時代意義的垃圾收集器-----CMS收集器,這款收集器是HotSpot虛擬機中第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集線程與用戶線程同時工做。
優點:
在單CPU中的環境中,不會比Serail收集器有更好的效果,由於存在線程交互開銷,甚至因爲線程交互的開銷,該收集器在兩個CPU的環境中都不能百分百保證能夠超越Serial收集器。固然,隨着可用CPU數量的增長,它對於GC時系統資源的有效利用仍是頗有好處的,它默認開啓的收集線程數與CPU數量相同。
設置參數:
"-XX:+UseConcMarkSweepGC":指定使用CMS後,會默認使用ParNew做爲新生代收集器;
"-XX:+UseParNewGC":強制指定使用ParNew;
"-XX:ParallelGCThreads":指定垃圾收集的線程數量,ParNew默認開啓的收集線程與CPU的數量相同;
ParNew/Serial Old組合收集器運行示意圖以下:
2.3 Parallel Scavenge 收集器
Parallel Scavenge收集器是一個新生代收集器,它也是使用複製算法的收集器,也是並行的多線程收集器。
對比分析:
Parallel Scavenge收集器 VS CMS等收集器:
Parallel Scavenge收集器的特色是它的關注點與其餘收集器不一樣,CMS等收集器的關注點是儘量地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。
因爲與吞吐量關係密切,Parallel Scavenge收集器也常常稱爲「吞吐量優先」收集器。
Parallel Scavenge收集器 VS ParNew收集器:
Parallel Scavenge收集器與ParNew收集器的一個重要區別是它具備自適應調節策略。
應用場景:
Parallel Scavenge收集器是虛擬機運行在Server模式下的默認垃圾收集器。
停頓時間短適合須要與用戶交互的程序,良好的響應速度能提高用戶體驗;高吞吐量則能夠高效率利用CPU時間,儘快完成運算任務,主要適合在後臺運算而不須要太多交互的任務。
該收集器以高吞吐量爲目標,就是減小垃圾收集時間,從而讓用戶代碼得到更長的運行時間。因此適合那些運行在多個CPU上,而且專一於後臺計算的應用程序,例如:執行批量處理任務、訂單處理,工資支付,科學計算等。
設置參數:
虛擬機提供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio兩個參數來精確控制最大垃圾收集停頓時間和吞吐量大小。不過不要覺得前者越小越好,GC停頓時間的縮短是以犧牲吞吐量和新生代空間換取的。
"-XX:+MaxGCPauseMillis":控制最大垃圾收集停頓時間,大於0的毫秒數;這個參數設置的越小,停頓時間可能會縮短,但也會致使吞吐量降低,致使垃圾收集發生得更頻繁。
"-XX:GCTimeRatio":設置垃圾收集時間佔總時間的比率,0<n<100的整數,就至關於設置吞吐量的大小。
垃圾收集執行時間佔應用程序執行時間的比例的計算方法是:
1 / (1 + n)
例如,選項-XX:GCTimeRatio=19,設置了垃圾收集時間佔總時間的5%--1/(1+19);
默認值是1%--1/(1+99),即n=99;
垃圾收集所花費的時間是年輕一代和老年代收集的總時間;
GC自適應的調節策略:
Parallel Scavenge收集器有一個參數-XX:+UseAdaptiveSizePolicy
。當這個參數打開以後,就不須要手工指定新生代的大小、Eden與Survivor區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式稱爲GC自適應的調節策略(GC Ergonomics)。若是對於垃圾收集器運做原理不太瞭解,以致於在優化比較困難的時候,使用Parallel收集器配合自適應調節策略,把內存管理的調優任務交給虛擬機去完成將是一個不錯的選擇。
2.4 Serial Old收集器
特性
Serial Old是Serial收集器的老年代版本,它一樣是一個單線程收集器,使用標記-整理算法。
應用場景
Client模式
Serial Old收集器的主要意義也是在於給Client模式下的虛擬機使用。
Server模式
若是在Server模式下,那麼它主要還有兩大用途:一種用途是在JDK 1.5以及以前的版本中與Parallel Scavenge收集器搭配使用;另外一種用途就是做爲CMS收集器的後備預案,在併發收集發生"Concurrent Mode Failure"時使用。
Serial/Serial Old收集器運行示意圖以下:
2.5 Parallel Old收集器
特性:
Parallel 收集器的老年代版本,使用多線程和」標記-整理「算法。
應用場景:
在注重吞吐量以及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器。
這個收集器是在JDK1.6中才開始提供的,特別是在Server模式,多CPU的狀況下。
設置參數:
"-XX:+UseParallelOldGC":指定使用Parallel Old收集器;
Parallel Scavenge/Parallel Old收集器運行示意圖以下:
2.6 CMS收集器
特性:
CMS(Concurrent Mark Sweep)收集器:基於」標記-清除「算法實現的(不進行壓縮,會產生內存碎片),特色是:併發收集,低停頓。
是HotSpot在JDK1.5推出的第一款真正意義上的併發(Concurrent)收集器;第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工做;
應用場景
與用戶交互較多的場景。CMS 收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的Java應用集中在互聯網或者B/S系統的服務端上,這類應用尤爲注重服務的響應速度,但願系統停頓時間最短,以給用戶帶來極好的體驗。CMS收集器就很是符合這類應用的需求。
運做過程:
對於前面幾種收集器來講更復雜一些,整個過程分爲4個步驟:
初始標記(CMS initial mark)
初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,但須要「Stop The World」。
併發標記(CMS concurrent mark)
併發標記階段就是進行GC Roots Tracing的過程,剛纔產生的集合中標記出存活對象;應用程序也在運行;並不能保證能夠標記出全部的存活對象;
從新標記(CMS remark)
從新標記階段是爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄;仍然須要須要」Stop The World「,這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短。
併發清除(CMS concurrent sweep)
併發清除階段會清除對象,回收全部的垃圾對象。
因爲整個過程當中耗時最長的併發標記和併發清除過程收集器線程均可以與用戶線程一塊兒工做,因此,從整體上來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
缺點:
CMS收集器對CPU資源很是敏感
其實,面向併發設計的程序都對CPU資源比較敏感。在併發階段,它雖然不會致使用戶線程停頓,可是會由於佔用了一部分線程(或者說CPU資源)而致使應用程序變慢,總吞吐量會下降。
CMS默認啓動的回收線程數=(CPU數量+3)/ 4,也就是當CPU在4個以上時,併發回收時垃圾收集線程很多於25%的CPU資源,而且隨着CPU數量的增長而降低。可是當CPU不足4個(譬如2個)時,CMS對用戶程序的影響就可能變得更大,可能會沒法接受。
瞭解:
增量式併發收集器:針對上述這種狀況,曾出現了」增量式併發收集器「,相似使用搶佔式來模擬多任務機制的思想,讓收集線程和用戶線程交替運行,減小收集線程運行時間;但效果並不理想,JDK1.6後官方就再也不提倡用戶使用。
CMS收集器沒法處理浮動垃圾
CMS收集器沒法處理浮動垃圾,可能出現「Concurrent Mode Failure」失敗而致使另外一次Full GC的產生。
因爲CMS併發清理階段用戶線程還在運行着,伴隨程序運行天然就還會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS沒法在當次收集中處理掉它們,只好留待下一次GC時再清理掉。這一部分垃圾就稱爲「浮動垃圾」。
因爲在垃圾收集階段用戶線程還須要運行,那就還須要預留有足夠的內存空間給用戶線程使用,所以CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集,也能夠熱爲CMS所須要的空間比其餘垃圾收集器大;
"-XX:CMSInitiatingOccupancyFraction":設置CMS預留內存空間;
JDK1.5默認值爲68%;
JDK1.6變爲大約92%;
(2):"Concurrent Mode Failure"失敗:
若是CMS運行期間預留的內存沒法知足程序須要,就會出現一次「Concurrent Mode Failure」失敗,這時虛擬機將啓動後備預案:臨時啓用Serial Old收集器來從新進行老年代的垃圾收集,這樣會致使另外一次Full GC的產生。這樣停頓時間就更長了,代價會更大,因此 "-XX:CMSInitiatingOccupancyFraction"不能設置得太大。
CMS收集器會產生大量空間碎片
CMS是一款基於「標記—清除」算法實現的收集器,清除後不進行壓縮操做,這意味着收集結束時會有大量空間碎片產生。
空間碎片過多時,將會給大對象分配帶來很大麻煩,每每會出現老年代還有很大空間剩餘,可是沒法找到足夠大的連續空間來分配當前對象,不得不提早觸發一次Full GC。
解決辦法:
(1)、"-XX:+UseCMSCompactAtFullCollection"
使得CMS出現上面這種狀況時不進行Full GC,而開啓內存碎片的合併整理過程; 但合併整理過程沒法併發,停頓時間會變長; 默認開啓(但不會進行,結合下面的CMSFullGCsBeforeCompaction); (2)、"-XX:+CMSFullGCsBeforeCompaction"
設置執行多少次不壓縮的Full GC後,來一次壓縮整理; 爲減小合併整理過程的停頓時間; 默認爲0,也就是說每次都執行Full GC,不會進行壓縮整理;
運行示意圖以下:
2.7 G1 收集器
G1(Garbage-First)是一款面向服務端應用的垃圾收集器,JDK 7 Update4 後開始進入商用。HotSpot開發團隊賦予它的使命是將來能夠替換掉JDK 1.5中發佈的CMS收集器。
在G1以前的其餘收集器進行收集的範圍都是整個新生代或者老年代,而G1再也不是這樣。使用G1收集器時,Java堆的內存佈局就與其餘收集器有很大差異,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔離的了,它們都是一部分Region(不須要連續)的集合。
G1收集器之因此能創建可預測的停頓時間模型,是由於它能夠有計劃地避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃份內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內能夠獲取儘量高的收集效率。
與其餘GC收集器相比,G1具有以下特色。
特性:
並行與併發
G1能充分利用多CPU、多核環境下的硬件優點,使用多個CPU來縮短Stop-The-World停頓的時間,部分其餘收集器本來須要停頓Java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓Java程序繼續執行。
分代收集(收集範圍包括新生代和老年代)
與其餘收集器同樣,分代概念在G1中依然得以保留。G1能夠不須要其餘收集器配合就能獨立管理整個GC堆,它可以採用不一樣的方式去處理不一樣時期的對象。使用G1收集器時,Java堆的內存佈局有了很大差異,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔離的了,它們都是一部分Region(不須要連續)的集合。
空間整合(結合多種垃圾收集算法,不產生碎片)
與CMS的「標記—清理」算法不一樣,G1從總體來看是基於「標記—整理」算法實現的收集器,從局部(兩個Region之間)上來看是基於「複製」算法實現的,但不管如何,這兩種算法都意味着G1運做期間不會產生內存空間碎片,收集後能提供規整的可用內存。這種特性有利於程序長時間運行,分配大對象時不會由於沒法找到連續內存空間而提早觸發下一次GC。
可預測的停頓(低停頓的同時實現高吞吐量)
這是G1相對於CMS的另外一大優點,下降停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能創建可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過N毫秒。
應用場景:
1.面向服務端應用,針對具備大內存、多處理器的機器;最主要的應用是爲須要低GC延遲,並具備大堆的應用程序提供解決方案;
如:在堆大小約6GB或更大時,可預測的暫停時間能夠低於0.5秒;
2.用來替換掉JDK1.5的CMS收集器;
(1)、超過50%的Java堆被活動數據佔用;
(2)、對象分配頻率或年代提高頻率變化很大;
(3)、GC停頓時間過長(長與0.5至1秒)。
是否必定採用G1呢?也未必:
若是如今採用的收集器沒有出現問題,不用着急去選擇G1;
若是應用程序追求低停頓,能夠嘗試選擇G1;
是否替代CMS須要實際場景測試才知道。
3.執行過程:
G1收集器的運做大體可劃分爲如下幾個步驟:
初始標記(Initial Marking)
初始標記階段僅僅只是標記一下GC Roots能直接關聯到的對象,而且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中建立新對象,這階段須要停頓線程,但耗時很短。
併發標記(Concurrent Marking)
併發標記階段是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時較長,但可與用戶程序併發執行。
最終標記(Final Marking)
最終標記階段是爲了修正在併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程Remembered Set Logs
裏面,最終標記階段須要把Remembered Set Logs
的數據合併到Remembered Set
中,這階段須要停頓線程,可是可並行執行。
篩選回收(Live Data Counting and Evacuation)
篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所指望的GC停頓時間來制定回收計劃,這個階段其實也能夠作到與用戶程序一塊兒併發執行,可是由於只回收一部分價值高的Region區的垃圾對象,時間是用戶可控制的,並且停頓用戶線程將大幅提升收集效率。回收時,採用「複製」算法,從一個或多個Region複製存活對象到堆上的另外一個空的Region,而且在此過程當中壓縮和釋放內存。
4.運行示意圖以下:
設置參數:
"-XX:+UseG1GC":指定使用G1收集器;
"-XX:InitiatingHeapOccupancyPercent":當整個Java堆的佔用率達到參數值時,開始併發標記階段;默認爲45;
"-XX:MaxGCPauseMillis":爲G1設置暫停時間目標,默認值爲200毫秒;
"-XX:G1HeapRegionSize":設置每一個Region大小,範圍1MB到32MB;目標是在最小Java堆時能夠擁有約2048個
3.垃圾收集器總結
來看一下對垃圾收集器的總結,列了一張表
GC組合 |
Minor GC |
Full GC |
描述 |
-XX:+UseSerialGC | Serial收集器串行回收 | Serial Old收集器串行回收 | 該選項能夠手動指定Serial收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParNewGC | ParNew收集器並行回收 | Serial Old收集器串行回收 | 該選項能夠手動指定ParNew收集器+Serilal Old組合執行內存回收 |
-XX:+UseParallelGC | Parallel收集器並行回收 | Serial Old收集器串行回收 | 該選項能夠手動指定Parallel收集器+Serial Old收集器組合執行內存回收 |
-XX:+UseParallelOldGC | Parallel收集器並行回收 | Parallel Old收集器並行回收 | 該選項能夠手動指定Parallel收集器+Parallel Old收集器組合執行內存回收 |
-XX:+UseConcMarkSweepGC | ParNew收集器並行回收 | 缺省使用CMS收集器併發回收,備用採用Serial Old收集器串行回收 |
該選項能夠手動指定ParNew收集器+CMS收集器+Serial Old收集器組合執行內存回收。優先使用ParNew收集器+CMS收集器的組合,當出現ConcurrentMode Fail或者Promotion Failed時,則採用ParNew收集器+Serial Old收集器的組合 |
-XX:+UseConcMarkSweepGC -XX:-UseParNewGC |
Serial收集器串行回收 | ||
-XX:+UseG1GC | G1收集器併發、並行執行內存回收 | 暫無 |
GC日誌
每種收集器的日誌形式都是由它們自身的實現所決定的,換言之,每種收集器的日誌格式均可以不同。不過虛擬機爲了方便用戶閱讀,將各個收集器的日誌都維持了必定的共性,就以最前面的對象間相互引用的那個類ReferenceCountingGC的代碼爲例:
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseSerialGC」,使用Serial+Serial Old組合進行垃圾回收的日誌
[GC [DefNew: 310K->194K(2368K), 0.0269163 secs] 310K->194K(7680K), 0.0269513 secs] [Times: user=0.00 sys=0.00, real=0.03 secs] [GC [DefNew: 2242K->0K(2368K), 0.0018814 secs] 2242K->2241K(7680K), 0.0019172 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System) [Tenured: 2241K->193K(5312K), 0.0056517 secs] 4289K->193K(7680K), [Perm : 2950K->2950K(21248K)], 0.0057094 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 2432K, used 43K [0x00000000052a0000, 0x0000000005540000, 0x0000000006ea0000) eden space 2176K, 2% used [0x00000000052a0000, 0x00000000052aaeb8, 0x00000000054c0000) from space 256K, 0% used [0x00000000054c0000, 0x00000000054c0000, 0x0000000005500000) to space 256K, 0% used [0x0000000005500000, 0x0000000005500000, 0x0000000005540000) tenured generation total 5312K, used 193K [0x0000000006ea0000, 0x00000000073d0000, 0x000000000a6a0000) the space 5312K, 3% used [0x0000000006ea0000, 0x0000000006ed0730, 0x0000000006ed0800, 0x00000000073d0000) compacting perm gen total 21248K, used 2982K [0x000000000a6a0000, 0x000000000bb60000, 0x000000000faa0000) the space 21248K, 14% used [0x000000000a6a0000, 0x000000000a989980, 0x000000000a989a00, 0x000000000bb60000) No shared spaces configured.
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseParNewGC」,使用ParNew+Serial Old組合進行垃圾回收的日誌
[GC [ParNew: 310K->205K(2368K), 0.0006664 secs] 310K->205K(7680K), 0.0007043 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC [ParNew: 2253K->31K(2368K), 0.0032525 secs] 2253K->2295K(7680K), 0.0032911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System) [Tenured: 2264K->194K(5312K), 0.0054415 secs] 4343K->194K(7680K), [Perm : 2950K->2950K(21248K)], 0.0055105 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap par new generation total 2432K, used 43K [0x0000000005550000, 0x00000000057f0000, 0x0000000007150000) eden space 2176K, 2% used [0x0000000005550000, 0x000000000555aeb8, 0x0000000005770000) from space 256K, 0% used [0x0000000005770000, 0x0000000005770000, 0x00000000057b0000) to space 256K, 0% used [0x00000000057b0000, 0x00000000057b0000, 0x00000000057f0000) tenured generation total 5312K, used 194K [0x0000000007150000, 0x0000000007680000, 0x000000000a950000) the space 5312K, 3% used [0x0000000007150000, 0x0000000007180940, 0x0000000007180a00, 0x0000000007680000) compacting perm gen total 21248K, used 2982K [0x000000000a950000, 0x000000000be10000, 0x000000000fd50000) the space 21248K, 14% used [0x000000000a950000, 0x000000000ac39980, 0x000000000ac39a00, 0x000000000be10000) No shared spaces configured.
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseParallelGC」,使用Parallel+Serial Old組合進行垃圾回收的日誌
[GC [PSYoungGen: 4417K->288K(18688K)] 4417K->288K(61440K), 0.0007910 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System) [PSYoungGen: 288K->0K(18688K)] [PSOldGen: 0K->194K(42752K)] 288K->194K(61440K) [PSPermGen: 2941K->2941K(21248K)], 0.0032663 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] Heap PSYoungGen total 18688K, used 321K [0x0000000034190000, 0x0000000035660000, 0x0000000048f90000) eden space 16064K, 2% used [0x0000000034190000,0x00000000341e05c0,0x0000000035140000) from space 2624K, 0% used [0x0000000035140000,0x0000000035140000,0x00000000353d0000) to space 2624K, 0% used [0x00000000353d0000,0x00000000353d0000,0x0000000035660000) PSOldGen total 42752K, used 194K [0x000000000a590000, 0x000000000cf50000, 0x0000000034190000) object space 42752K, 0% used [0x000000000a590000,0x000000000a5c0810,0x000000000cf50000) PSPermGen total 21248K, used 2982K [0x0000000005190000, 0x0000000006650000, 0x000000000a590000) object space 21248K, 14% used [0x0000000005190000,0x0000000005479980,0x0000000006650000)
虛擬機參數爲「-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC」,使用ParNew+CMS+Serial Old組合進行垃圾回收的日誌
[Full GC (System) [CMS: 0K->194K(62656K), 0.0080796 secs] 4436K->194K(81792K), [CMS Perm : 2941K->2940K(21248K)], 0.0081589 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] Heap par new generation total 19136K, used 340K [0x0000000005540000, 0x0000000006a00000, 0x0000000006a00000) eden space 17024K, 2% used [0x0000000005540000, 0x0000000005595290, 0x00000000065e0000) from space 2112K, 0% used [0x00000000065e0000, 0x00000000065e0000, 0x00000000067f0000) to space 2112K, 0% used [0x00000000067f0000, 0x00000000067f0000, 0x0000000006a00000) concurrent mark-sweep generation total 62656K, used 194K [0x0000000006a00000, 0x000000000a730000, 0x000000000a940000) concurrent-mark-sweep perm gen total 21248K, used 2981K [0x000000000a940000, 0x000000000be00000, 0x000000000fd40000)
這四段GC日誌中提煉出一些共性:
一、日誌的開頭「GC」、「Full GC」表示此次垃圾收集的停頓類型,而不是用來區分新生代GC仍是老年代GC的。若是有Full,則說明本次GC中止了其餘全部工做線程。看到Full GC的寫法是「Full GC(System)」,這說明是調用System.gc()方法所觸發的GC。
二、「GC」中接下來的「DefNew」、「ParNew」、「PSYoungGen」、「CMS」表示的是老年代垃圾收集器的名稱,「PSYoungGen」中的「PS」指的是「Parallel Scavenge」,它是Parallel收集器的全稱。
三、以第一個爲例,方括號內部的「320K->194K(2368K)」、「2242K->0K(2368K)」,指的是該區域已使用的容量->GC後該內存區域已使用的容量(該內存區總容量)。方括號外面的「310K->194K(7680K)」、「2242K->2241K(7680K)」則指的是GC前Java堆已使用的容量->GC後Java堆已使用的容量(Java堆總容量)。
四、還以第一個爲例,再日後「0.0269163 secs」表示該內存區域GC所佔用的時間,單位是秒。最後的「[Times: user=0.00 sys=0.00 real=0.03 secs]」則更具體了,user表示用戶態消耗的CPU時間、內核態消耗的CPU時間、操做從開始到結束通過的鐘牆時間。後面兩個的區別是,鍾牆時間包括各類非運算的等待消耗,好比等待磁盤I/O、等待線程阻塞,而CPU時間不包括這些耗時,但當系統有多CPU或者多核的話,多線程操做會疊加這些CPU時間因此若是user或sys超過real是徹底正常的。
五、「Heap」後面就列舉出堆內存目前各個年代的區域的內存狀況
4.觸發GC的時機
最後總結一下何時會觸發一次GC,我的經驗看,有三種場景會觸發GC:
一、第一種場景應該很明顯,當年輕代或者老年代滿了,Java虛擬機沒法再爲新的對象分配內存空間了,那麼Java虛擬機就會觸發一次GC去回收掉那些已經不會再被使用到的對象
二、手動調用System.gc()方法,一般這樣會觸發一次的Full GC以及至少一次的Minor GC
三、程序運行的時候有一條低優先級的GC線程,它是一條守護線程,當這條線程處於運行狀態的時候,天然就觸發了一次GC了。這點也很好證實,不過要用到WeakReference的知識,後面寫WeakReference的時候會專門講到這個。J