本文来探讨如何分析GC日志。探讨的话题包括:

  • JDK 8:
    • GC日志打印相关的JVM参数
    • 如何分析日志
  • JDK 11:
    • GC日志打印相关的JVM参数
    • 如何分析日志
  • GC日志可视化分析工具

    JDK 8

    GC日志打印相关参数

    | 参数 | 作用 | 默认值 | | —- | —- | —- | | -XX:+PrintGC | 输出GC日志 | 关闭 | | -XX:+PrintGCDetails | 打印GC的详情 | 关闭 | | -XX:+PrintGCCause | 是否在GC日志中打印造成GC的原因 | 打开 | | -XX:+PrintGCID | 打印垃圾GC的唯一标识 | 关闭 | | -XX:+PrintGCDateStamps | 以日期的格式输出GC的时间戳,如 2013-05-04T21:53:59.234+0800 | 关闭 | | -XX:+PrintGCTimeStamps | 以基准时间的格式,打印GC的时间戳 | 关闭 | | -XX:+PrintGCTaskTimeStamps | 为每个GC工作线程的任务打印时间戳 | 关闭 | | -XX:+PrintHeapAtGC | 在GC前后打印堆信息 | 关闭 | | -XX:+PrintHeapAtGCExtended | 在开启PrintHeapAtGC的前提下,额外打印更多堆相关的信息 | 关闭 | | -XX:+PrintGCApplicationStoppedTime | 打印垃圾回收期间程序暂停的时间 | 关闭 | | -XX:+PrintGCApplicationConcurrentTime | 打印每次垃圾回收前,程序未中断的执行时间,可与PrintGCApplicationStoppedTime配合使用 | 关闭 | | -XX:+PrintClassHistogramAfterFullGC | Full GC之后打印堆直方图 | 关闭 | | -XX:+PrintClassHistogramBeforeFullGC | Full GC之前打印堆直方图 | 关闭 | | -XX:+PrintReferenceGC | 打印处理引用对象的时间消耗,需开启PrintGCDetails才有效 | 关闭 | | -XX:+PrintTLAB | 查看TLAB空间的使用情况 | 关闭 | | -XX:-UseGCLogFileRotation | 轮换文件,日志文件达到一定大小后,就创建一个新的日志文件。需指定-Xloggc:时才有效。 | 关闭 | | -XX:GCLogFileSize | 设置单个日志文件的大小,需开启UseGCLogFileRotation才有效 | 8KB | | -XX:NumberOfGCLogFiles | 日志轮换时,保留几个日志文件,默认0,保留所有日志 | 0 | | -Xloggc:文件路径 | 指定GC日志文件路径 | - |

另外,开启如下参数,可打印GC相关的更多信息,帮助我们更好地分析G1日志:

参数 作用 默认值
-XX:+PrintAdaptiveSizePolicy 某些GC收集器有自适应策略,自适应调整策略会动态调整Eden、Survivor、老年代的大小。使用该标记,可打印自适应调节策略相关的信息。参考文档:https://www.jianshu.com/p/7414fd6862c5 关闭
-XX:+PrintTenuringDistribution 查看每次minor GC后新的存活周期的阈值。Desired survivor size 1048576 bytes, new threshold 7 (max 15)。其中,7新的存活周期的阈值为7 关闭

示例:

  1. java -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+UseSerialGC -Xmx50m -Xloggc:/Users/itmuch.com/gc.log xxx.jar

Young GC日志

  1. 2020-05-08T11:11:52.823-0800: 0.705: [GC (Allocation Failure) 2020-05-08T11:11:52.823-0800: 0.705: [DefNew: 15289K->1343K(15360K), 0.0036231 secs] 18555K->5065K(49536K), 0.0037065 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

其中:

  • 2020-05-08T11:11:52.823-0800:当前时间戳,由PrintGCDateStamps控制
  • 0.705:当前相对时间戳,表示应用启动多久后触发,由PrintGCTimeStamps控制
  • GC (Allocation Failure):造成GC的原因,由PrintGCCause控制
  • [DefNew: 15289K->1343K(15360K), 0.0036231 secs]:
    • DefNew:使用不同垃圾收集器,这里的展示不同:
      • 使用Serial收集器:显示DefNew,表示Default New
      • 使用ParNew收集器:显示ParNew
      • 使用Paralle Scavenge收集器:显示PSYoungGen
      • 使用G1:G1格式和这个日志格式不一样,很好区分
    • 15289K:回收前,年轻代使用的大小
    • 1343K:回收后,年轻代使用的大小
    • 15360K:年轻代总大小
    • 0.0036231:花费了多久
  • 18555K:回收前,堆使用的大小
  • 5065K:回收后,堆使用的大小
  • 49536K:堆的总大小
  • 0.0037065 secs:花费时间
  • user=0.00:用户耗时
  • sys=0.00:系统耗时
  • real=0.00:实际耗时

    Full GC日志

    1. 2020-05-08T11:28:16.911-0800: 7.863: [Full GC (Allocation Failure) 2020-05-08T11:28:16.911-0800: 7.863: [Tenured: 6847K->6848K(6848K), 0.0329351 secs] 9914K->9483K(9920K), [Metaspace: 30156K->30156K(1077248K)], 0.0330357 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
  • 2020-05-08T11:28:16.911-0800:当前时间戳,由PrintGCDateStamps控制

  • 7.863:当前相对时间戳,表示应用启动多久后触发,由PrintGCTimeStamps控制
  • Full GC (Allocation Failure) :造成GC的原因,由PrintGCCause控制
  • [Tenured: 6847K->6848K(6848K), 0.0329351 secs]
    • Tenured:使用不同垃圾收集器,这里的展示不同:
      • 使用Serial Old收集器:显示Tenured
      • 使用Parallel Old收集器:显示ParOldGen
      • 使用CMS收集器:显示CMS
    • 6847K:回收前,老年代使用的大小
    • 6848K:回收后,老年代使用的大小
    • 6848K:老年代总大小
    • 0.0329351:花费时间
  • 9914K:回收前,堆使用的大小
  • 9483K:回收后,堆使用的大小
  • 9920K:堆的总大小
  • [Metaspace: 30156K->30156K(1077248K)], 0.0330357 secs]:元空间的使用情况
  • [Times: user=0.03 sys=0.00, real=0.03 secs] :同新生代日志

    G1日志

    如使用的是G1垃圾收集器,那么在上面表格的基础上,还有如下参数:
参数 作用 默认值
-XX:G1PrintRegionLivenessInfo 标记阶段结束后打印所有region的存活情况,,需开启-XX:+UnlockDiagnosticVMOptions后才能使用 关闭
-XX:+G1PrintHeapRegions 打印堆的区域上的分配和释放的信息,需开启-XX:+UnlockDiagnosticVMOptions后才能使用 关闭
-XX:+PrintStringDeduplicationStatistics JDK 8u20开始,使用G1垃圾收集器,可支持用-XX:+UseStringDeduplication开启字符串去重。用rintStringDeduplicationStatistics打印字符串去重的统计信息。参考文档: https://blog.csdn.net/goldenfish1919/article/details/94555589 关闭

Young GC日志

示例:

  1. 2020-05-08T14:19:58.598-0800: 5.599: [GC pause (G1 Evacuation Pause) (young), 0.0035900 secs]
  2. [Parallel Time: 2.4 ms, GC Workers: 8]
  3. [GC Worker Start (ms): Min: 5598.7, Avg: 5599.1, Max: 5600.8, Diff: 2.1]
  4. [Ext Root Scanning (ms): Min: 0.0, Avg: 0.3, Max: 0.7, Diff: 0.7, Sum: 2.4]
  5. [Update RS (ms): Min: 0.0, Avg: 0.8, Max: 1.8, Diff: 1.8, Sum: 6.4]
  6. [Processed Buffers: Min: 0, Avg: 2.2, Max: 6, Diff: 6, Sum: 18]
  7. [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
  8. [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
  9. [Object Copy (ms): Min: 0.0, Avg: 0.2, Max: 0.4, Diff: 0.4, Sum: 1.7]
  10. [Termination (ms): Min: 0.0, Avg: 0.5, Max: 0.7, Diff: 0.7, Sum: 3.7]
  11. [Termination Attempts: Min: 1, Avg: 2.4, Max: 12, Diff: 11, Sum: 19]
  12. [GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.9, Diff: 0.9, Sum: 0.9]
  13. [GC Worker Total (ms): Min: 0.2, Avg: 1.9, Max: 2.3, Diff: 2.2, Sum: 15.3]
  14. [GC Worker End (ms): Min: 5601.0, Avg: 5601.0, Max: 5601.0, Diff: 0.0]
  15. [Code Root Fixup: 0.0 ms]
  16. [Code Root Purge: 0.0 ms]
  17. [Clear CT: 0.1 ms]
  18. [Other: 1.1 ms]
  19. [Choose CSet: 0.0 ms]
  20. [Ref Proc: 0.7 ms]
  21. [Ref Enq: 0.0 ms]
  22. [Redirty Cards: 0.3 ms]
  23. [Humongous Register: 0.0 ms]
  24. [Humongous Reclaim: 0.0 ms]
  25. [Free CSet: 0.0 ms]
  26. [Eden: 4096.0K(4096.0K)->0.0B(4096.0K) Survivors: 1024.0K->1024.0K Heap: 24.3M(30.0M)->20.6M(30.0M)]
  27. [Times: user=0.01 sys=0.00, real=0.01 secs]

解读:

  1. # 这是一个年轻代GC,花费了0.0018345。下面的缩进,表示这行日志的子任务
  2. 2020-05-08T14:19:58.598-0800: 5.599: [GC pause (G1 Evacuation Pause) (young), 0.0035900 secs]
  3. # 并行任务,并行GC花费2.4毫秒,并行阶段有8个线程
  4. [Parallel Time: 2.4 ms, GC Workers: 8]
  5. # 表示各个GC工作线程在应用启动多久(毫秒)后启动。
  6. # 同时还做了个统计,例如这些GC线程最早启动的那个线程在应用启动后847.9毫秒后启动等
  7. [GC Worker Start (ms): Min: 5598.7, Avg: 5599.1, Max: 5600.8, Diff: 2.1]
  8. # 表示各个GC工作线程扫描跟对象花费的时间的统计
  9. [Ext Root Scanning (ms): Min: 0.0, Avg: 0.3, Max: 0.7, Diff: 0.7, Sum: 2.4]
  10. # 表示各个GC工作线程更新Remembered Sets花费的时间的统计
  11. # Remembered Sets是保存到堆中的区域的跟踪引用。设值方法线程持续改变对象图,自此引指向一个特定的区域。我们保存这些改变的跟踪信息到叫作Update Buffers的更新缓存中。Update RS子任务不能并发的处理更新缓存,更新一致所有区域的Remembered Sets
  12. [Update RS (ms): Min: 0.0, Avg: 0.8, Max: 1.8, Diff: 1.8, Sum: 6.4]
  13. # 表示每个GC工作线程处理的Update Buffers的数量统计
  14. [Processed Buffers: Min: 0, Avg: 2.2, Max: 6, Diff: 6, Sum: 18]
  15. # 每个GC工作线程扫描Remembered Sets花费的时间
  16. # 一个区域的Remembered Sets包含指向这个区域的引用的相符合的卡片。这个阶段扫描这些卡片寻找指向所有这些区域的Collection Set的引用
  17. [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
  18. # 扫描Code Root耗时统计。Code Root是JIT编译后的代码里引用了heap中的对象
  19. [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
  20. # 拷贝存活对象到新的Region耗时统计
  21. [Object Copy (ms): Min: 0.0, Avg: 0.2, Max: 0.4, Diff: 0.4, Sum: 1.7]
  22. # 各个GC工作线程完成任务后尝试中断GC线程到真正中断的耗时统计
  23. # 在某个GC线程中断之前,会检查其它线程的工作队列,如果发现依然有任务,会帮助处理,之后再中断
  24. [Termination (ms): Min: 0.0, Avg: 0.5, Max: 0.7, Diff: 0.7, Sum: 3.7]
  25. # 尝试中断次数统计
  26. [Termination Attempts: Min: 1, Avg: 2.4, Max: 12, Diff: 11, Sum: 19]
  27. # GC工作线程花费在其他工作上的时间统计
  28. [GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.9, Diff: 0.9, Sum: 0.9]
  29. # 各个GC工作线程花费的时间总和统计
  30. [GC Worker Total (ms): Min: 0.2, Avg: 1.9, Max: 2.3, Diff: 2.2, Sum: 15.3]
  31. # 各个GC工作线程线程的结束时间,min|max分别表示第一个|最后一个线程的结束时间。
  32. [GC Worker End (ms): Min: 5601.0, Avg: 5601.0, Max: 5601.0, Diff: 0.0]
  33. # 串行任务,修复GC期间code root指针改变的耗时
  34. [Code Root Fixup: 0.0 ms]
  35. # 串行任务,清除Code Root耗时
  36. [Code Root Purge: 0.0 ms]
  37. # 清除Card Table中的Dirty Card的耗时
  38. [Clear CT: 0.1 ms]
  39. # 其他任务
  40. [Other: 1.1 ms]
  41. # 为Collection Set选择区域所花费的时间
  42. [Choose CSet: 0.0 ms]
  43. # 花费在处理引用对象上的时间
  44. [Ref Proc: 0.7 ms]
  45. # 引用入队到ReferenceQueues花费的时间,可用-XX:+ParallelRefProcEnabled,并行处理这一步
  46. [Ref Enq: 0.0 ms]
  47. [Redirty Cards: 0.3 ms]
  48. # 处理超大对象
  49. [Humongous Register: 0.0 ms]
  50. [Humongous Reclaim: 0.0 ms]
  51. # 释放Collection Set数据结构花费的时间
  52. [Free CSet: 0.0 ms]
  53. # 各个区域的内存变化。
  54. # 4096.0K:伊甸园当前占用4096.0K
  55. # (4096.0K):伊甸园总大小4096.0K
  56. # 0.0B:收集后,伊甸园占用将会变成0
  57. # (4096.0K):伊甸园的目标大小(如有需要,JVM可能会自动增加伊甸园大小)
  58. [Eden: 4096.0K(4096.0K)->0.0B(4096.0K) Survivors: 1024.0K->1024.0K Heap: 24.3M(30.0M)->20.6M(30.0M)]
  59. # 用户耗时、系统耗时、实际耗时
  60. [Times: user=0.01 sys=0.00, real=0.01 secs]

并发回收日志

  1. # 1. 初始标记(stop the world)
  2. 2020-05-08T14:19:54.076-0800: 1.076: [GC pause (Metadata GC Threshold) (young) (initial-mark), 0.0107606 secs]
  3. [Parallel Time: 8.2 ms, GC Workers: 8]
  4. [GC Worker Start (ms): Min: 1076.6, Avg: 1077.1, Max: 1077.5, Diff: 0.9]
  5. [Ext Root Scanning (ms): Min: 0.1, Avg: 2.9, Max: 4.6, Diff: 4.5, Sum: 23.2]
  6. [Update RS (ms): Min: 0.0, Avg: 0.4, Max: 1.4, Diff: 1.4, Sum: 3.4]
  7. [Processed Buffers: Min: 0, Avg: 1.8, Max: 7, Diff: 7, Sum: 14]
  8. [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
  9. [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
  10. [Object Copy (ms): Min: 0.0, Avg: 0.5, Max: 1.5, Diff: 1.5, Sum: 4.2]
  11. [Termination (ms): Min: 0.0, Avg: 2.6, Max: 4.3, Diff: 4.3, Sum: 20.9]
  12. [Termination Attempts: Min: 1, Avg: 2.4, Max: 6, Diff: 5, Sum: 19]
  13. [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
  14. [GC Worker Total (ms): Min: 4.4, Avg: 6.5, Max: 8.0, Diff: 3.6, Sum: 51.9]
  15. [GC Worker End (ms): Min: 1082.0, Avg: 1083.6, Max: 1084.7, Diff: 2.7]
  16. [Code Root Fixup: 0.0 ms]
  17. [Code Root Purge: 0.0 ms]
  18. [Clear CT: 0.1 ms]
  19. [Other: 2.4 ms]
  20. [Choose CSet: 0.0 ms]
  21. [Ref Proc: 2.2 ms]
  22. [Ref Enq: 0.0 ms]
  23. [Redirty Cards: 0.1 ms]
  24. [Humongous Register: 0.0 ms]
  25. [Humongous Reclaim: 0.0 ms]
  26. [Free CSet: 0.0 ms]
  27. [Eden: 1024.0K(10.0M)->0.0B(10.0M) Survivors: 2048.0K->2048.0K Heap: 15.3M(30.0M)->14.4M(30.0M)]
  28. [Times: user=0.01 sys=0.00, real=0.01 secs]
  29. # 开始扫描初始标记阶段Survivor区的Root Region
  30. 2020-05-08T14:19:54.087-0800: 1.087: [GC concurrent-root-region-scan-start]
  31. # 扫描完成
  32. 2020-05-08T14:19:54.091-0800: 1.092: [GC concurrent-root-region-scan-end, 0.0049225 secs]
  33. # 2. 并发标记,标记线程数可用-XX:ConcGCThreads指定
  34. 2020-05-08T14:19:54.092-0800: 1.092: [GC concurrent-mark-start]
  35. # 并发标记结束
  36. 2020-05-08T14:19:54.102-0800: 1.103: [GC concurrent-mark-end, 0.0106528 secs]
  37. # 3. 最终标记(stop the world)
  38. 2020-05-08T14:19:54.103-0800: 1.104: [GC remark 2020-05-08T14:19:54.103-0800: 1.104: [Finalize Marking, 0.0028699 secs] 2020-05-08T14:19:54.106-0800: 1.107: [GC ref-proc, 0.0001689 secs] 2020-05-08T14:19:54.106-0800: 1.107: [Unloading, 0.0053988 secs], 0.0087250 secs]
  39. [Times: user=0.01 sys=0.00, real=0.01 secs]
  40. # 4. 筛选回收(stop the world)
  41. # 没有存活对象的Old Region和Humongous Region将被释放和清空。
  42. # 为了准备下次GC,在CSets中的Old Regions会根据他们的回收收益的大小排序。
  43. 2020-05-08T14:19:54.114-0800: 1.114: [GC cleanup 15M->14M(30M), 0.0006027 secs]
  44. [Times: user=0.00 sys=0.00, real=0.00 secs]
  45. # 并发清理开始
  46. 2020-05-08T14:19:54.114-0800: 1.115: [GC concurrent-cleanup-start]
  47. # 并发清理结束
  48. 2020-05-08T14:19:54.114-0800: 1.115: [GC concurrent-cleanup-end, 0.0000133 secs]

Full GC

  1. 2020-05-08T14:19:57.869-0800: 4.869: [Full GC (Allocation Failure) 28M->18M(30M), 0.0789569 secs]
  2. [Eden: 0.0B(1024.0K)->0.0B(7168.0K) Survivors: 0.0B->0.0B Heap: 28.4M(30.0M)->18.2M(30.0M)], [Metaspace: 44204K->44114K(1089536K)]
  3. [Times: user=0.11 sys=0.00, real=0.08 secs]

G1日志官方解读

https://blogs.oracle.com/poonam/understanding-g1-gc-logs

JDK 11

GC日志打印相关参数

从JDK 9开始,GC日志由“统一日志管理”(Xlog)管理。GC日志相关的JVM参数只剩如下两个,并且这两个参数也用Xlog代替。有关统一日志管理,详见《JDK 11统一日志管理》一文。

参数 作用 默认值
-XX:+PrintGC 输出GC日志 关闭
-XX:+PrintGCDetails 打印GC的详情 关闭

示例:

  1. -Xmx30m -XX:+UseSerialGC -Xlog:gc*:file=/Users/itmuch.com/gc11.log

日志解读

  1. # 打印使用的垃圾收集器
  2. # 0.015s指的是应用启动后过了多久
  3. [0.015s][info][gc] Using Serial
  4. # 打印内存概览,例如堆内存地址、堆内存总大小、压缩指针模式等
  5. [0.015s][info][gc,heap,coops] Heap address: 0x00000007fe200000, size: 30 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
  6. # 发生了年轻代GC,原因是Allocation Failure
  7. # GC(0)中的0,是对垃圾收集的次数统计,从0开始
  8. [0.213s][info][gc,start ] GC(0) Pause Young (Allocation Failure)
  9. # 年轻代占用情况:回收前占用8181K,回收后占用1023K,年轻代总大小9216K
  10. [0.216s][info][gc,heap ] GC(0) DefNew: 8181K->1023K(9216K)
  11. # 老年代占用情况:回收前、回收后、总大小
  12. [0.216s][info][gc,heap ] GC(0) Tenured: 0K->1548K(20480K)
  13. # 元数据占用情况:回收前、回收后、总大小
  14. [0.216s][info][gc,metaspace ] GC(0) Metaspace: 5645K->5645K(1056768K)
  15. # 整个堆的占用情况:回收前、回收后、总大小
  16. [0.216s][info][gc ] GC(0) Pause Young (Allocation Failure) 7M->2M(29M) 3.642ms
  17. # 用户耗时、系统耗时、实际耗时
  18. [0.217s][info][gc,cpu ] GC(0) User=0.00s Sys=0.00s Real=0.00s

综上,其实和JDK 8的日志打印区别并不大,只是打印的格式更加整齐,输出更加清晰了。同理,其他垃圾收集器也是类似的。

GC日志可视化分析工具