人生并不像火車要通過每個站似的經過每一個生活階段。人生總是直向前行走,從不留下什么。 —— 劉易斯
GC日志理解
每一種收集器的日志格式都可以不一樣的。 以下是兩段典型的GC日志:
0.173: [GC 0.173: [DefNew: 1353K->582K(19008K), 0.0015600 secs]0.175: [Tenured: 0K->581K(42368K), 0.0027216 secs] 1353K->581K(61376K), [Metaspace: 2551K->2551K(1056768K)], 0.0043860 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.275: [Full GC (System.gc()) 0.275: [Tenured: 205381K->205382K(247172K), 0.0035190 secs] 205722K->205382K(266308K), [Metaspace: 2551K->2551K(1056768K)], 0.0041874 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
最前面的數字"0.173"和"0.275"代表了GC發生的時間,這個數字的含義是從JVM啟動以來經過的秒數。
??GC日志開頭的“[GC”和“[Full GC”說明了這次垃圾收集的停頓類型。如果有Full,說明這次GC是發生了Stop the world的。如果是調用System.gc(),則是顯示“[Full GC (System.gc())”。
??“[DefNew”、“[Tenured”、“Perm”(JDK1.7及以下)、“[Metaspace”(jdk1.8)表示發生GC的區域。上面示例中使用的Serial收集器中的新生代名為“Default New Generation”,所以顯示“[DefNew”。如果是ParNew收集器,新生代名稱就會變為“ParNew”,意為“Parallel New Genaration”。如果采用Parallel Scavenge收集器,那新生代名稱就會變為“PSYoungGen”。老年代和永久代同理。
??“1353K->582K(19008K)”表示“GC前該內存區域已使用容量->GC后該內存區域已使用容量(該內存區域總容量)”,方括號外部的“1353K->581K(61376K)”表示“GC前JAVA堆已使用容量->GC后JAVA堆已使用容量(JAVA堆總容量)”。
??“0.0015600 secs”表示該內存區域GC所占用的時間,單位是秒。“[Times: user=0.00 sys=0.00, real=0.00 secs]”分別代表用戶態消耗的CPU時間、內核態消耗的CPU時間和操作從開始到結束的墻鐘時間。CPU時間和墻鐘時間的區別是墻鐘時間包括各種非運算的等待耗時,如等待磁盤IO、等待線程阻塞等,而CPU時間不包括這些耗時。
JVM的GC日志的主要參數
-XX:+PrintGC 輸出GC日志
-XX:+PrintGCDetails 輸出GC的詳細日志
-XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式)
-XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC 在進行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的輸出路徑
GC類型
在HotSpot中,GC分為兩大種類:
-
Partial GC: 不收集整個堆得GC
??Young GC: 只收集young gen的GC。
??Old GC:只收集old gen的GC。只有CMS的concurrent collection是這個模式。
??Mixed GC:收集young gen和部分old gen的GC,只有G1有這個模式。 - Full GC:收集整個堆,包括young gen、old gen、perm gen(如果存在的話)等區域。
PS: Major GC通常是跟full GC是等價的,收集整個GC堆。
GC的觸發條件
- Young GC:當young gen中的eden區分配滿的時候觸發。
-
Full GC:
??① 當準備要觸發一次young GC時,如果發現統計數據說之前young GC的平均晉升大小比目前old gen剩余的空間大,則不會執行Young GC而是轉為執行Full GC(因為HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都會同時收集整個GC堆,包括young gen,所以不需要事先觸發一次單獨的young GC
)。
??② 如果有perm gen的話,在perm gen上分配空間且沒有足夠空間時,也要觸發Full GC。
??③ System.gc(),顯示的調用GC,也會觸發Full GC。
??④ Old gen空間不足:當創建一個大對象、大數組時,eden 區不足以分配這么大的空間,會嘗試在old gen 中分配,如果這時 old gen 空間也不足時,會觸發 full gc。
??⑤ ygc出現 promotion failure(晉升失敗):promotion failure 發生在 young gc 階段,即 cms 的 ParNewGC,當對象的gc年齡達到閾值時,或者 eden 的 to 區放不下時,會把該對象復制到 old gen,如果 old gen 空間不足時,會發生 promotion failure,并接下去觸發full gc。
finalize詳解
對象object重寫了finalize()方法,且還未執行過,那么object會被插入到F-Queue隊列中,由一個虛擬機自動創建的、低優先級的Finalizer線程觸發其finalize()方法。finalize()方法是對象逃脫死亡的最后機會,GC會對隊列中的對象進行第二次標記。如果object在finalize()方法中與引用鏈上的任何一個對象建立聯系,那么在第二次標記時,object會被移出“即將回收”集合.
??finalize只會被執行一次,下面通過例子來說明下finalize。
public class FinalizeTest {
public static FinalizeTest object;
byte[] _200M = new byte[200 * 1024 * 1024];
public void isAlive() {
System.out.println("I'm alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("method finalize is running");
object = this;
}
public static void main(String[] args) throws InterruptedException {
object = new FinalizeTest();
object = null;
System.gc();
Thread.sleep(500);
if (object != null) {
object.isAlive();
} else {
System.out.println("I'm dead");
}
object = null;
System.gc();
Thread.sleep(500);
if (object != null) {
object.isAlive();
} else {
System.out.println("I'm dead");
}
}
}
執行程序后,程序輸出結果:
method finalize is running
I'm alive
I'm dead
第一次GC時,因在finalize
方法中,將當前對象賦值給了object,因此第一次未被回收。而第二次GC時,由于finalize
方法已經執行過了,因finalize
方法只會被JVM調用一次,所以第二次GC時,object被回收了。