转载

一分钟读懂 JVM 的 GC 日志

阅读GC日志是一件很有意义的事情,通过阅读GC日志,我们可以了解到JVM的内存分配和回收策略。本文不打算讨论JVM的构成和垃圾回收算法,阅读本文前需要读者有这方面的知识储备。

我们准备如下代码:

public class Test01 {
	public static void main(String[] args) {
		byte[] b1 = new byte[4*1024*1024];
		byte[] b2 = new byte[4*1024*1024];
		byte[] b3 = new byte[4*1024*1024];
		byte[] b4 = new byte[4*1024*1024];
	}
}

JVM启动参数为:

-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags

GC日志:

运行代码,我们会得到类似下面的GC日志:

[GC (Allocation Failure) [PSYoungGen: 796K->504K(1536K)] 14108K->13896K(15360K), 0.0042751 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 504K->502K(1536K)] [ParOldGen: 13392K->13368K(13824K)] 13896K->13870K(15360K), [Metaspace: 2735K->2735K(1056768K)], 0.0073296 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

详细解释如下,为了便于阅读,我把对应的解释标记成了不同的颜色:

GC & Full GC :表示垃圾回收时的停顿类型,GC针对年轻代;Full GC针对老年代,说明这次GC是发生了Stop-The-World,此时收集会暂停所有工作线程,会进行堆内存全扫描。这也是为什么不建议在程序中显式调用System.gc(),因为它会触发Full GC。

[PSYoungGen: 796K->504K(1536K)] :796K表示GC前的Young区占用;504K表示GC后的Young区占用;1536K表示Yong区总大小。

14108K->13896K(15360K) :14108K表示YoungGC前的整个堆的占用;13896K表示YoungGC后的整个堆的占用;15360K表示整个堆的大小

0.0042751 secs :YoungGC的耗时。

[Times: user=0.06 sys=0.00, real=0.00 secs] :user用户态消耗CPU时间;sys内核消耗CPU时间;real标识从开始到结束所经过的墙钟时间(Wall Clock Time)。墙钟时间的意思是,从进程从开始运行到结束,时钟走过的时间,这其中包含了进程在阻塞和等待状态的时间,包含了各种非运算耗时,比如磁盘I/O耗时,线程阻塞等待时间等,而user和sys不包含这些,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以user或sys时间超过real时间是完全正常的。

我们还可以看到堆内存信息,类似如下:

Heap
 PSYoungGen      total 1536K, used 531K [0x00000000ff980000, 0x00000000ffc80000, 0x0000000100000000)
  eden space 1024K, 3% used [0x00000000ff980000,0x00000000ff987c68,0x00000000ffa80000)
  from space 512K, 97% used [0x00000000ffa80000,0x00000000ffafd208,0x00000000ffb00000)
  to   space 512K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x00000000ffc80000)
 ParOldGen       total 13824K, used 13357K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)
  object space 13824K, 96% used [0x00000000fec00000,0x00000000ff90b798,0x00000000ff980000)
 Metaspace       used 2767K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 298K, capacity 386K, committed 512K, reserved 1048576K

详细解释如下,为了便于阅读,我把对应的解释标记成了不同的颜色:

PSYoungGen :垃圾回收类型,PS是Parallel Scavenge收集器的缩写,表示年轻代使用了Parallel Scavenge,它对应的新生代称为PSYoungGen,新生代又分为eden space、from space和to space这三部分。

需要注意的是,不同的新生代垃圾回收算法对应的名字也不相同,会有如下三种新生代垃圾回收算法,读者可以自行修改下面给出的参数,运行感受一下:

GC[ParNew,表示使用的是parNew收集器,对应的参数为 -XX:+UseSerialGC。

GC[DefNew,表示用的是serial收集器,对应的参数为 -XX:+UseParNewGC。

GC[PSYoungGen,表示用的是Parallel Scavenge收集器,对应的参数为  -XX:+UseParallelGC。

ParOldGen :Parallel Scavenge收集器配套的老年代。

它与新生代一样,不同的垃圾回收算法对应的名字也不相同:

tenured generation,表示使用的是Serial Old收集器。

ParOldGen,表示使用的是Parallel Old收集器。

concurrent mark-sweep generation,表示使用的是CMS收集器。

Metaspace :永久代。

total & used :总的空间和用掉的空间。

总结:

1、JVM采用的是分代收集,不同的对象生命周期是不一样的,使用不同垃圾回收算法也不同,这是为了提高回收效率。

2、没有最好的收集器,也没有万能的收集器,只有最适合的收集器。

3、可以根据日志中分区的名称推断出使用的垃圾回收器。

4、垃圾回收期之间可以组合使用,在公司中比较常见的搭配为  -XX:+UseParNewGC -XX:+UseConcMarkSweepGC,新生代使用ParNew,老年代使用CMS。当然使用CMS的前提是,可以提供更大的内存和更多的CPU(堆最少为3G内存以上,建议最小4G),也就是硬件比较好。

5、使用CMS垃圾回收时,通常还会配置如下几个参数:

-XX:+UseCMSInitiatingOccupancyOnly:

仅仅使用手动定义(CMSInitiatingOccupancyFraction)开始CMS收集。

-XX:CMSInitiatingOccupancyFraction=70:

CMS在年老代, 使用70%后开始CMS收集,如果你的年老代增长不是那么快,并且希望降低CMS次数的话,可以适当调高此值。

-XX:+CMSParallelRemarkEnabled:开启并行Remark,默认是开启的,所以可以不设置此参数。

6、经过我的测试,G1并没有比CMS表现更好,相反,CMS无论是吞吐率还是停顿时间,都比G1表现更好,不能相信网上的大话。当然这并不一定是CMS比G1更好,可能某些情况下,G1可能比CMS会好,只是我现在还不确定,如果有了解的同学欢迎留言。

在文章的最后,我为大家准备了一份JVM参数模板,供大家参考,关注微信公众号 “大熊的技术轶事”或者扫描下面二维码,回复“JVM参数”即可获取。

一分钟读懂 JVM 的 GC 日志

原文  https://mp.weixin.qq.com/s/DloKDxkZfUuL_O-oABnk5Q
正文到此结束
Loading...