实战-通过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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章