天天看点

JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析

GC日志的解读和分析

Java可以通过以下命令指定启动GC日志的打印:

#命令  #打印GC日志详情      #打印GC事件参数       #运行的类名,注这里是类名不是class文件名不要后缀
java -XX:+PrintGCDetails -XX:PrintGCDateStamps XXXX(类名)
           

关于jvm启动的一些默认堆内存参数信息

1. 如果当前宿主机物理内存大于1g的情况下会默认使用1/4的物理内存作为jvm的堆内存大小
2. 如果当前宿主机的物理内存<=1g,jvm默认会使用当前物理内存大小的一半为堆内存的大小
           

在启动的时候可以通过 -Xms -Xmx 来设置堆内存的大小,两个参数最好设置一样大小,防止运行的过程jvm自调节堆内存大小带来的性能抖动。

什么是性能抖动?

2021-08-14T13:15:58.662-0800: [GC (Allocation Failure) [PSYoungGen: 65536K->10738K(76288K)] 65536K->20104K(251392K), 0.0107299 secs] [Times: user=0.02 sys=0.05, real=0.01 secs] 
2021-08-14T13:15:58.686-0800: [GC (Allocation Failure) [PSYoungGen: 76274K->10737K(141824K)] 85640K->40977K(316928K), 0.0151278 secs] [Times: user=0.02 sys=0.06, real=0.02 secs] 
2021-08-14T13:15:58.754-0800: [GC (Allocation Failure) [PSYoungGen: 141809K->10734K(141824K)] 172049K->80395K(316928K), 0.0263950 secs] [Times: user=0.03 sys=0.12, real=0.03 secs] 
2021-08-14T13:15:58.802-0800: [GC (Allocation Failure) [PSYoungGen: 141806K->10748K(272896K)] 211467K->126140K(448000K), 0.0272760 secs] [Times: user=0.04 sys=0.13, real=0.03 secs] 
2021-08-14T13:15:58.933-0800: [GC (Allocation Failure) [PSYoungGen: 272892K->10751K(272896K)] 388284K->212632K(475136K), 0.0355946 secs] [Times: user=0.05 sys=0.17, real=0.03 secs] 
2021-08-14T13:15:58.969-0800: [Full GC (Ergonomics) [PSYoungGen: 10751K->0K(272896K)] [ParOldGen: 201880K->177802K(347136K)] 212632K->177802K(620032K), [Metaspace: 2679K->2679K(1056768K)], 0.0350235 secs] [Times: user=0.19 sys=0.01, real=0.04 secs] 
2021-08-14T13:15:59.055-0800: [GC (Allocation Failure) [PSYoungGen: 262144K->76269K(208384K)] 439946K->258937K(555520K), 0.0382796 secs] [Times: user=0.07 sys=0.16, real=0.04 secs] 
2021-08-14T13:15:59.119-0800: [GC (Allocation Failure) [PSYoungGen: 208365K->108539K(240640K)] 391033K->295700K(587776K), 0.0164485 secs] [Times: user=0.10 sys=0.00, real=0.02 secs] 
2021-08-14T13:15:59.165-0800: [GC (Allocation Failure) [PSYoungGen: 240635K->108543K(225280K)] 427796K->327872K(572416K), 0.0340029 secs] [Times: user=0.14 sys=0.05, real=0.03 secs] 
2021-08-14T13:15:59.223-0800: [GC (Allocation Failure) [PSYoungGen: 225225K->92407K(232960K)] 444555K->354088K(580096K), 0.0347330 secs] [Times: user=0.08 sys=0.11, real=0.03 secs] 
2021-08-14T13:15:59.281-0800: [GC (Allocation Failure) [PSYoungGen: 209143K->65157K(232960K)] 470824K->377037K(580096K), 0.0409836 secs] [Times: user=0.08 sys=0.15, real=0.04 secs] 
2021-08-14T13:15:59.322-0800: [Full GC (Ergonomics) [PSYoungGen: 65157K->0K(232960K)] [ParOldGen: 311879K->274485K(452096K)] 377037K->274485K(685056K), [Metaspace: 2679K->2679K(1056768K)], 0.0575322 secs] [Times: user=0.31 sys=0.01, real=0.05 secs] 
2021-08-14T13:15:59.402-0800: [GC (Allocation Failure) [PSYoungGen: 116736K->37907K(232960K)] 391221K->312392K(685056K), 0.0066941 secs] [Times: user=0.04 sys=0.00, real=0.00 secs] 
2021-08-14T13:15:59.437-0800: [GC (Allocation Failure) [PSYoungGen: 154643K->31976K(232960K)] 429128K->340860K(685056K), 0.0162042 secs] [Times: user=0.08 sys=0.00, real=0.02 secs] 
2021-08-14T13:15:59.478-0800: [GC (Allocation Failure) [PSYoungGen: 148712K->42767K(232960K)] 457596K->378788K(685056K), 0.0175845 secs] [Times: user=0.04 sys=0.06, real=0.02 secs] 
2021-08-14T13:15:59.522-0800: [GC (Allocation Failure) [PSYoungGen: 159384K->44810K(232960K)] 495405K->419897K(685056K), 0.0298354 secs] [Times: user=0.07 sys=0.08, real=0.03 secs] 
2021-08-14T13:15:59.578-0800: [GC (Allocation Failure) [PSYoungGen: 161546K->36455K(232960K)] 536633K->452191K(685056K), 0.0268303 secs] [Times: user=0.06 sys=0.08, real=0.03 secs] 
2021-08-14T13:15:59.604-0800: [Full GC (Ergonomics) [PSYoungGen: 36455K->0K(232960K)] [ParOldGen: 415736K->316905K(496128K)] 452191K->316905K(729088K), [Metaspace: 2679K->2679K(1056768K)], 0.0626508 secs] [Times: user=0.33 sys=0.01, real=0.06 secs] 
执行结束!共生成对象次数: 7600
Heap
 PSYoungGen      total 232960K, used 5054K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 116736K, 4% used [0x00000007aab00000,0x00000007aafefb20,0x00000007b1d00000)
  from space 116224K, 0% used [0x00000007b1d00000,0x00000007b1d00000,0x00000007b8e80000)
  to   space 116224K, 0% used [0x00000007b8e80000,0x00000007b8e80000,0x00000007c0000000)
 ParOldGen       total 496128K, used 316905K [0x0000000780000000, 0x000000079e480000, 0x00000007aab00000)
  object space 496128K, 63% used [0x0000000780000000,0x000000079357a478,0x000000079e480000)
 Metaspace       used 2686K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 294K, capacity 386K, committed 512K, reserved 1048576K
           

在上面的示例中,可以看到没有配置大小的时候,JVM在启动的时候会自动设置一个大小,然后触发full GC以后会自动扩容当前的大小,直到达到最大值,在这个扩容的过程中,会触发多次FullGC导致多次的整体的STW,这个就叫性能抖动,为了保证性能稳定降低性能抖动带来的影响,所以一般默认设置Xms和Xmx为相同的大小。

打印GC操作日志示例:

java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xms1g -Xmx1g GCLogAnalysis
           

操作疑问:

     当我选用jdk8的时候,这个命令行执行ok的,但是当我把jdk环境切到11以后会出现如下错误:

Unrecognized VM option 'PrintGCDateStamps'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
           

检查发现如下:

[0.004s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[0.019s][info   ][gc,heap] Heap region size: 1M
[0.032s][info   ][gc     ] Using G1
[0.032s][info   ][gc,heap,coops] Heap address: 0x00000007c0000000, size: 1024 MB,
           

是的在jdk9中已经取消了PrintGCDetails的打印命令,改用Xlog:gc*的方式进行打印,通过通过上面的记录信息可以看出来,jdk11默认的GC处理处理器就是G1 GC方式了

接下来看一下详细的GC日志记录信息

[GC (Allocation Failure) [PSYoungGen: 65536K->10741K(76288K)] 65536K->20344K(251392K), 0.0082267 secs] [Times: user=0.01 sys=0.04, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 12109K->0K(58368K)] [ParOldGen: 164920K->147554K(175104K)] 177030K->147554K(233472K), [Metaspace: 2679K->2679K(1056768K)], 0.0280932 secs] [Times: user=0.15 sys=0.01, real=0.03 secs] 
           

以上分别是PSYongGC和FullGC的两条记录,下面用图示大概说明下对应的信息

PSYongGC

JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析

FullGC 

JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析

 gc的日志详情和gc的日期参数信息,JDK8下用默认GC算法打印参考信息如下:

2021-08-14T12:35:23.733-0800: [GC (Allocation Failure) [PSYoungGen: 262144K->43518K(305664K)] 262144K->75447K(1005056K), 0.0334441 secs] [Times: user=0.04 sys=0.15, real=0.03 secs] 
2021-08-14T12:35:23.814-0800: [GC (Allocation Failure) [PSYoungGen: 305662K->43519K(305664K)] 337591K->150441K(1005056K), 0.0591339 secs] [Times: user=0.07 sys=0.30, real=0.06 secs] 
2021-08-14T12:35:23.911-0800: [GC (Allocation Failure) [PSYoungGen: 305663K->43508K(305664K)] 412585K->218083K(1005056K), 0.0459681 secs] [Times: user=0.10 sys=0.18, real=0.04 secs] 
2021-08-14T12:35:24.007-0800: [GC (Allocation Failure) [PSYoungGen: 305652K->43513K(305664K)] 480227K->290175K(1005056K), 0.0466027 secs] [Times: user=0.09 sys=0.20, real=0.05 secs] 
2021-08-14T12:35:24.100-0800: [GC (Allocation Failure) [PSYoungGen: 305657K->43518K(305664K)] 552319K->361546K(1005056K), 0.0423480 secs] [Times: user=0.08 sys=0.18, real=0.04 secs] 
2021-08-14T12:35:24.192-0800: [GC (Allocation Failure) [PSYoungGen: 305662K->43519K(160256K)] 623690K->434804K(859648K), 0.0446934 secs] [Times: user=0.09 sys=0.19, real=0.04 secs] 
2021-08-14T12:35:24.258-0800: [GC (Allocation Failure) [PSYoungGen: 160255K->72492K(232960K)] 551540K->469214K(932352K), 0.0126632 secs] [Times: user=0.06 sys=0.01, real=0.02 secs] 
2021-08-14T12:35:24.293-0800: [GC (Allocation Failure) [PSYoungGen: 189188K->87874K(232960K)] 585910K->493792K(932352K), 0.0181114 secs] [Times: user=0.08 sys=0.02, real=0.02 secs] 
2021-08-14T12:35:24.337-0800: [GC (Allocation Failure) [PSYoungGen: 204610K->106146K(232960K)] 610528K->528001K(932352K), 0.0295913 secs] [Times: user=0.13 sys=0.05, real=0.03 secs] 
2021-08-14T12:35:24.394-0800: [GC (Allocation Failure) [PSYoungGen: 222882K->76456K(232960K)] 644737K->558662K(932352K), 0.0418105 secs] [Times: user=0.08 sys=0.13, real=0.04 secs] 
2021-08-14T12:35:24.463-0800: [GC (Allocation Failure) [PSYoungGen: 193192K->41372K(232960K)] 675398K->591617K(932352K), 0.0508881 secs] [Times: user=0.07 sys=0.18, real=0.05 secs] 
2021-08-14T12:35:24.536-0800: [GC (Allocation Failure) [PSYoungGen: 157926K->36011K(232960K)] 708171K->620694K(932352K), 0.0336914 secs] [Times: user=0.06 sys=0.09, real=0.03 secs] 
执行结束!共生成对象次数: 8898
Heap
 PSYoungGen      total 232960K, used 116999K [0x00000007aab00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 116736K, 69% used [0x00000007aab00000,0x00000007afa16ec0,0x00000007b1d00000)
  from space 116224K, 30% used [0x00000007b8e80000,0x00000007bb1aadf0,0x00000007c0000000)
  to   space 116224K, 0% used [0x00000007b1d00000,0x00000007b1d00000,0x00000007b8e80000)
 ParOldGen       total 699392K, used 584682K [0x0000000780000000, 0x00000007aab00000, 0x00000007aab00000)
  object space 699392K, 83% used [0x0000000780000000,0x00000007a3afaa30,0x00000007aab00000)
 Metaspace       used 2686K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 294K, capacity 386K, committed 512K, reserved 1048576K
           

GC异常的示例

JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析

如上图:

yong区的大小为532.56m,old区的大小为7.35G,总计堆大小为大概8G。如图所示,yong区总共发生了481次gc耗时4分31秒平均每次GC的时间是 560ms左右,old区总共发生了118gc耗时31分22秒平均每次gc的时间是16秒,而且如上图所示每次old区的full GC 基本没有清空多少时间,这种高耗时低效率的gc处理就是一个严重的gc退化情况,此时需要检查线程运行环境的jvm参数是否需要调整,系统本身是否发生了内存泄漏。

GC日志分析第三方工具

 1. GCEasy

使用方式,通过浏览器打开 http://gceasy.io,在页面上上传GC的日志文件

JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析
JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析

 2. GCViewer

        使用方式,通过浏览器搜索并下载jar包,然后在本地通过命令行运行,

下载地址:https://github.com/chewiebug/gcviewer

参考如下图: 

JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析
JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析
JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析
JVM核心知识学习心得指北GC日志的解读和分析GC日志解读分析

GC日志解读分析

        几个常见的GC处理日志信息和基础解读见链接:        https://blog.csdn.net/huxingmale/article/details/119700655

以下是我个人的一些总结:

        首先jvm的堆内存参数只是一个设置值,实际运行当中目前统计下来是你设置的参数的95%大小,不知道是否和内存大小计算有关,一般来说jvm运行大小建议至少256m,设置过小很容易触发OOM的出现。

 低内存(256m~以下)

        在256m这个大小的堆内存下运行jvm,整体来说从GC运行效率来说并行GC>CMSGC>串行GC,每次处理的耗时和触发FullGC的概率都是非常优秀的,但是从触发FullGC这点来说不得不说CMSGC特别的强,在256M的堆内存环境运行下,它竟然可以一次都不触发,整体的回收效率非常的优秀,但是因为本身针对老年代的回收上它耗费了更多的cpu资源和时间,所以略逊并行GC一筹。同时,因为并行GC有一个自适应的默认参数,可以通过日志解读发现,当JVM启动的时候堆内存是最大的251392K,

2021-08-14T14:56:24.102-0800: [GC (Allocation Failure) [PSYoungGen: 65452K->10749K(76288K)] 65452K->24321K(251392K), 0.0098329 secs] [Times: user=0.01 sys=0.05, real=0.01 secs] 

但是当运行一段时间后我们会惊讶的发现

2021-08-14T14:56:24.273-0800: [GC (Allocation Failure) [PSYoungGen: 76055K->10742K(40448K)] 178632K->137696K(215552K), 0.0147039 secs] [Times: user=0.03 sys=0.05, real=0.01 secs] 

没错,总内存变小了,这里是因为自适应参数的存在,GC的过程中并行算法会根据运行情况自动分配年轻代中的eden区和缓冲区的大小比例,如下:

2021-08-14T14:56:24.293-0800: [GC (Allocation Failure) [PSYoungGen: 40298K->17295K(58368K)] 167252K->147144K(233472K), 0.0028745 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 

2021-08-14T14:56:24.300-0800: [GC (Allocation Failure) [PSYoungGen: 46991K->26339K(58368K)] 176840K->159304K(233472K), 0.0067433 secs] [Times: user=0.03 sys=0.01, real=0.00 secs] 

在执行几次以后,jvm认为这个大小的比例是当前合理稳定的,于是保存下来。为了验证我这个想法,我在执行参数中加入了关闭自适应参数-XX:-UseAdaptiveSizePolicy ,于是符合预期想法,jvm运行过程中堆内存的整体大小不在变化。

具体区别如下图:

未关闭自适应参数前:

Heap

 PSYoungGen      total 58368K, used 29202K [0x00000007bab00000, 0x00000007c0000000, 0x00000007c0000000)

  eden space 29696K, 98% used [0x00000007bab00000,0x00000007bc784ab8,0x00000007bc800000)

  from space 28672K, 0% used [0x00000007be400000,0x00000007be400000,0x00000007c0000000)

  to   space 28672K, 0% used [0x00000007bc800000,0x00000007bc800000,0x00000007be400000)

 ParOldGen       total 175104K, used 174912K [0x00000007b0000000, 0x00000007bab00000, 0x00000007bab00000)

  object space 175104K, 99% used [0x00000007b0000000,0x00000007baad00e8,0x00000007bab00000)

 Metaspace       used 2686K, capacity 4486K, committed 4864K, reserved 1056768K

  class space    used 294K, capacity 386K, committed 512K, reserved 1048576K

关闭自适应参数后:

Heap

 PSYoungGen      total 76288K, used 62646K [0x00000007bab00000, 0x00000007c0000000, 0x00000007c0000000)

  eden space 65536K, 95% used [0x00000007bab00000,0x00000007be82db48,0x00000007beb00000)

  from space 10752K, 0% used [0x00000007beb00000,0x00000007beb00000,0x00000007bf580000)

  to   space 10752K, 0% used [0x00000007bf580000,0x00000007bf580000,0x00000007c0000000)

 ParOldGen       total 175104K, used 174706K [0x00000007b0000000, 0x00000007bab00000, 0x00000007bab00000)

  object space 175104K, 99% used [0x00000007b0000000,0x00000007baa9c898,0x00000007bab00000)

 Metaspace       used 2686K, capacity 4486K, committed 4864K, reserved 1056768K

  class space    used 294K, capacity 386K, committed 512K, reserved 1048576K

第一次GC的时候

2021-08-14T16:31:58.330-0800: [GC (Allocation Failure) [PSYoungGen: 65536K->10749K(76288K)] 65536K->21765K(251392K), 0.0099438 secs] [Times: user=0.01 sys=0.05, real=0.01 secs]

中内存(512m-1g)

        在这个堆内存阀值下,串行GC首先被排除,运行效率低,GC暂停时间长。由于G1GC在256M下直接就会OOM,所以起步就用了512M,在这个环境下个人感觉从现在开始G1GC的效率开始碾压其他几个GC了,如果说G1GC有什么缺点的话,可能就是它的详细GC日志比较难以解读,从纯日志层面上不如并行GC和CMSGC好解读。

        并行GC有着优秀的吞吐量和处理效率,但是GC的次数较多STW比较多,JVM的稳定性略差,CMSGC性能稳定GC次数少,但是并发销量和吞吐量略低,所以如果是追求高并发实时响应的系统考虑选择并行GC,最大程度的实现高响应和高吞吐;追求性能稳定的系统可以选择CMSGC,在保持系统高效处理的同时保证足够的性能稳定性。G1的话虽然性能非常优越,但是在这个层级上性能提升不是特别的大,日志又比较难以解读和监控,考虑到维护成本的业务场景下,个人觉的还是优先考虑CMS GC。

大内存(2G~以上)

        一般的java服务jvm运行内存其实不需要这么高,如果需要开到这么高的堆内存阀值的情况下,这个服务一般有以下特性:1. 高吞吐 2. 高并发,所以在这个层面上考虑的性能的最优化,G1GC我觉的最好的垃圾回收算法。