1.前言
垃圾收集器是前一章垃圾收集算法理論知識的具體實現了,不同虛擬機所提供的垃圾收集器可能會有很大差別,另外我們必須提前說明一個道理:沒有最好的垃圾收集器,更加沒有萬能的收集器,只能選擇對具體應用最合適的收集器。這也是HotSpot為什么要實現這么多收集器的原因,下面我們以HotSpot為例講解。在寫之前,先介紹幾個概念。
1.1.並行和並發的區別
這個區別之前在你專門的一節介紹過,這里再重點提一下:這兩個名詞都是並發編程中的概念,在談論垃圾收集器的上下文語境中,可以這么理解這兩個名詞:
1、並行Parallel
多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態。
2、並發Concurrent
指用戶線程與垃圾收集線程同時執行(但並不一定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另一個CPU上。
1.2.Minor GC和Full GC的區別
-
新生代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收集器無法處理浮動垃圾
(1):浮動垃圾:
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日志中提煉出一些共性:
1、日志的開頭“GC”、“Full GC”表示這次垃圾收集的停頓類型,而不是用來區分新生代GC還是老年代GC的。如果有Full,則說明本次GC停止了其他所有工作線程。看到Full GC的寫法是“Full GC(System)”,這說明是調用System.gc()方法所觸發的GC。
2、“GC”中接下來的“DefNew”、“ParNew”、“PSYoungGen”、“CMS”表示的是老年代垃圾收集器的名稱,“PSYoungGen”中的“PS”指的是“Parallel Scavenge”,它是Parallel收集器的全稱。
3、以第一個為例,方括號內部的“320K->194K(2368K)”、“2242K->0K(2368K)”,指的是該區域已使用的容量->GC后該內存區域已使用的容量(該內存區總容量)。方括號外面的“310K->194K(7680K)”、“2242K->2241K(7680K)”則指的是GC前Java堆已使用的容量->GC后Java堆已使用的容量(Java堆總容量)。
4、還以第一個為例,再往后“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是完全正常的。
5、“Heap”后面就列舉出堆內存目前各個年代的區域的內存情況
4.觸發GC的時機
最后總結一下什么時候會觸發一次GC,個人經驗看,有三種場景會觸發GC:
1、第一種場景應該很明顯,當年輕代或者老年代滿了,Java虛擬機無法再為新的對象分配內存空間了,那么Java虛擬機就會觸發一次GC去回收掉那些已經不會再被使用到的對象
2、手動調用System.gc()方法,通常這樣會觸發一次的Full GC以及至少一次的Minor GC
3、程序運行的時候有一條低優先級的GC線程,它是一條守護線程,當這條線程處於運行狀態的時候,自然就觸發了一次GC了。這點也很好證明,不過要用到WeakReference的知識,后面寫WeakReference的時候會專門講到這個。
轉載:
http://www.cnblogs.com/xrq730/p/4836700.html
http://blog.csdn.net/tjiyu/article/details/53983650
http://www.jianshu.com/p/50d5c88b272d