實戰-通過gc日誌觀察Java內存分配與回收策略

程序目的

通過gc日誌,詳細觀察java內存分配與回收策略

概要說明

  • 爲方便觀察垃圾回收,指定Java堆大小,並指定年輕代大小,這樣當Eden區內存不夠時,會觸發Minor GC
  • 通過-XX:+PrintGCDetails打印GC詳細信息,用於觀察Java內存分配過程
  • 根據日誌輸出結果,解釋內存分配過程

源碼

/**
 * <pre>
 * 程序目的:通過gc日誌,詳細觀察java內存分配與回收策略
 * 具體參數說明:
 * 		-verbose:gc	在發生內存回收時在輸出設備顯示信息
 * 		-Xms20M	初始堆大小20M
 * 		-Xmx20M	最大堆大小20M
 * 		-Xmn10M	年輕代大小
 * 		-XX:+PrintGCDetails	打印GC詳細信息
 * 		-XX:SurvivorRatio=8	設置年輕代中Eden區與Survivor區的比值。系統默認是8
 * 		-XX:+UseSerialGC	使用串行回收器進行回收,這個參數會使新生代和老年代都使用串行回收器,新生代使用複製算法,老年代使用標記-整理算法。
 * 		-XX:+PrintGCTimeStamps	輸出GC的時間戳(以基準時間的形式)
 * 		-XX:+PrintGCDateStamps	輸出GC的時間戳(以日期的形式,如:2020-06-14T15:15:24.806-0800)
 * 細則說明:
 * 	年輕代設置了10M、堆大小一共20M,則老年代大小爲10M Survivor區大小各佔1M
 * 期望結果:
 * 	allocation1、allocation2、allocation3分配時,佔用6M大小,這時還沒有把新生代佔滿。
 * 	當分配allocation4時,需要4M空間,新生代不夠用了,於是發生 Minor GC。
 * 	Minor GC期間,發現 Survivor空間只有1M,無法放入3個2M的,所以只好通過擔保機制提前轉移到老年代。
 * 	Minor GC結束,4M的allocation4順利分配在Eden區,老年代被佔用4M。
 * 	這次收集結束後,4MB的allocation4對象順利分配在Eden中。
 * 	因此程序執行完的結果是Eden佔用4MB(被allocation4佔用),Survivor空閒,老年代被佔用6MB(被allocation1、2、3佔用)。
 * </pre>
 * created at 2020-06-14 09:38
 * @author lerry
 */
public class MinorGcDemo {

	/**
	 * 1M 的字節數量
	 * 1024byte = 1K
	 * 1024K = 1M
	 */
	private static final int _1MB = 1024 * 1024;

	/**
	 * VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
	 * -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps
	 */
	public static void testAllocation() {
		byte[] allocation1, allocation2, allocation3, allocation4;
		allocation1 = new byte[2 * _1MB];
		allocation2 = new byte[2 * _1MB];
		allocation3 = new byte[2 * _1MB];
		// 出現一次Minor GC
		allocation4 = new byte[4 * _1MB];
	}

	public static void main(String[] args) {
		testAllocation();
	}
}

結果輸出如下:

2020-06-14T18:15:02.949-0800: 0.088: [GC (Allocation Failure) 2020-06-14T18:15:02.949-0800: 0.088: [DefNew: 6816K->281K(9216K), 0.0055718 secs] 6816K->6425K(19456K), 0.0056400 secs] [Times: user=0.01 sys=0.01, real=0.01 secs] 
Heap
 def new generation   total 9216K, used 2411K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  26% used [0x00000007bec00000, 0x00000007bee14930, 0x00000007bf400000)
  from space 1024K,  27% used [0x00000007bf500000, 0x00000007bf546578, 0x00000007bf600000)
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 tenured generation   total 10240K, used 6144K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,  60% used [0x00000007bf600000, 0x00000007bfc00030, 0x00000007bfc00200, 0x00000007c0000000)
 Metaspace       used 2687K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 290K, capacity 386K, committed 512K, reserved 1048576K

輸出結果解釋

首行輸出內容解釋

爲方便觀察,我們把第一行順序輸出,手動分隔,並進行標號

①2020-06-14T18:15:02.949-0800: 0.088:

輸出GC的日期、時間信息。
其中0.088指的是:JVM啓動到當期的總時長爲0.088秒

②GC

用來區分(distinguish)是 Minor GC 還是 Full GC 的標誌(Flag). 這裏的 GC 表明本次發生的是 Minor GC.

③(Allocation Failure)

引起垃圾回收的原因. 本次GC是因爲年輕代中沒有任何合適的區域能夠存放需要分配的數據結構而觸發的.

④2020-06-14T18:15:02.949-0800: 0.088

見①

⑤DefNew:

使用的垃圾收集器的名字. DefNew 這個名字代表的是: 
單線程(single-threaded), 
採用標記複製(mark-copy)算法的, 
使整個JVM暫停運行(stop-the-world)的年輕代(Young generation) 垃圾收集器(garbage collector).

⑥6816K->281K

在本次垃圾收集之前和之後的年輕代內存使用情況(Usage).
回收前,佔用6336K的新生代內存空間(6816➗1024=6.65625M)
回收後,佔用 281K 的新生代內存空間

⑦(9216K),

年輕代的總的大小(Total size).
9216K換算成M,爲9M,整9M

⑧0.0055718 secs

GC事件的持續時間(Duration),單位是秒.

⑨6816K->6425K

在本次垃圾收集之前和之後整個堆內存的使用情況(Total used heap).
可以和⑥對比,發現回收前,都是佔用 6816K 的內存空間,
回收後,整個堆內存佔用了 6425K(≈6.27M)內存空間。

①0⃣️(19456K)

總的可用的堆內存(Total available heap).
19456K=19M

①①0.0056400 secs

見⑧
GC事件的持續時間(Duration),單位是秒.
比⑧長了 0.0000682 秒

①②[Times: user=0.00 sys=0.00, real=0.00 secs]

GC事件的持續時間,通過多種分類來進行衡量。
這裏都是零、可能是因爲四捨五入了。
多執行幾遍程序,可能會收到類似這樣的輸出:
[Times: user=0.01 sys=0.01, real=0.01 secs] 
  • user – 此次垃圾回收, 垃圾收集線程消耗的所有CPU時間(Total CPU time).
  • sys – 操作系統調用(OS call) 以及等待系統事件的時間(waiting for system event)
  • real – 應用程序暫停的時間(Clock time). 由於串行垃圾收集器(Serial Garbage Collector)只會使用單個線程, 所以 real time 等於 user 以及 system time 的總和.

"Heap"後輸出解釋

from space 1024K, 27% used

第一次Minor GC後,見⑥(6816K->281K),有 281K 需要轉移到Survivor From區,這時 281K < 1M ,所以有足夠空間,放入Survivor from空間。
佔用比例爲: 281K ➗ 1024 = 0.27441406。
和結果輸出的 27% 接近

to space 1024K, 0% used

Survivor的to空間用於複製時的交換,需要留空,所以爲0M

tenured generation total 10240K, used 6144K
the space 10240K, 60% used

6144K,剛好6M。
被allocation1、2、3佔用

特殊情況說明

在使用1.8.0_144的jdk版本時,輸出結果爲
MinorGcDemo在jdk1.8.0_144版本的執行結果
圖:MinorGcDemo在jdk1.8.0_144版本的執行結果

老年代佔用比例爲:40%
可能是這個版本jdk的一個bug。

環境說明

  • java -version
java version "1.8.0_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)

  • OS:macOS High Sierra 10.13.4
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章