【译】深入理解G1的GC日志(一)
这篇文章将深入研究G1的日志和调优参数。为了在实际工作中对G1进行调优,作为开发者的你需要理解G1垃圾收集器的每个步骤,以及每个步骤在整个垃圾收集周期中的作用。为了方便读者学习,这篇文章将G1的日志参数分为等级递增的三块,这篇文章将会分别介绍每一部分参数的作用和调优时候使用的场景。
- 基础参数 - 在生产中使用G1收集器,必须使用这些参数
- 高级参数 - 随着应用的成熟或业务负载的增加,需要使用这些参数针对某些问题进行调优。
- Debug参数 - 这些参数是用来解决特定的性能问题,如果某个问题在非生产环境中无法复现,才会在生产环境中使用这些参数排查问题。
基础参数
如果你要在生产环境中使用G1 GC,下面这些跟日志相关的参数是必备的,有了这些参数,你才能排查基本的垃圾回收问题。
使用-XX:GCLogFileSize
设置合适的GC日志文件大小,使用-XX:NumberOfGCLogFiles
设置要保留的GC日志文件个数,使用-Xloggc:/path/to/gc.log
设置GC日志文件的位置,通过上面三个参数保留应用在运行过程中的GC日志信息,我建议最少保留一个星期的GC日志,这样应用的运行时信息足够多的,方便排查问题。
新生代收集
和其他垃圾收集器一样,G1也使用-XX:PrintGCDetails
打印出详细的垃圾收集日志,下面这张图是新生代收集的标准流程,我在这里将它分成了6个步骤:
- 四个关键信息
- 新生代垃圾收集发生的时间——2016-12-12T10:40:18.811-0500,通过设置
-XX:+PrintGCDateStamps
参数可以打印出这个时间; - JVM启动后的相对时间——25.959
- 这次收集的类型——新生代收集,只回收Eden分区
- 这次收集花费的时间——0.0305171s,即30ms
- 新生代垃圾收集发生的时间——2016-12-12T10:40:18.811-0500,通过设置
- 列出了新生代收集中并行收集的详细过程
- Parallel Time:并行收集任务在运行过程中引发的STW(Stop The World)时间,从新生代垃圾收集开始到最后一个任务结束,共花费26.6ms
- GC Workers:有4个线程负责垃圾收集,通过参数
-XX:ParallelGCThreads
设置,这个参数的值的设置,跟CPU有关,如果物理CPU支持的线程个数小于8,则最多设置为8;如果物理CPU支持的线程个数大于8,则默认值为number * 5/8 - GC Worker Start:第一个垃圾收集线程开始工作时JVM启动后经过的时间(min);最后一个垃圾收集线程开始工作时JVM启动后经过的时间(max);diff表示min和max之间的差值。理想情况下,你希望他们几乎是同时开始,即diff趋近于0。
- Ext Root Scanning:扫描root集合(线程栈、JNI、全局变量、系统表等等)花费的时间,扫描root集合是垃圾收集的起点,尝试找到是否有root集合中的节点指向当前的收集集合(CSet)
- Update RS(Remembered Set or RSet):每个分区都有自己的RSet,用来记录其他分区指向当前分区的指针,如果RSet有更新,G1中会有一个post-write barrier管理跨分区的引用——新的被引用的card会被标记为dirty,并放入一个日志缓冲区,如果这个日志缓冲区满了会被加入到一个全局的缓冲区,在JVM运行的过程中还有线程在并发处理这个全局日志缓冲区的dirty card。Update RS表示允许垃圾收集线程处理本次垃圾收集开始前没有处理好的日志缓冲区,这可以确保当前分区的RSet是最新的。
- Processed Buffers,这表示在Update RS这个过程中处理多少个日志缓冲区。
- Scan RS:扫描每个新生代分区的RSet,找出有多少指向当前分区的引用来自CSet。
- Code Root Scanning:扫描代码中的root节点(局部变量)花费的时间
- Object Copy:在疏散暂停期间,所有在CSet中的分区必须被转移疏散,Object Copy就负责将当前分区中存活的对象拷贝到新的分区。
- Termination:当一个垃圾收集线程完成任务时,它就会进入一个临界区,并尝试帮助其他垃圾线程完成任务(steal outstanding tasks),min表示该垃圾收集线程什么时候尝试terminatie,max表示该垃圾收集回收线程什么时候真正terminated。
- Termination Attempts:如果一个垃圾收集线程成功盗取了其他线程的任务,那么它会再次盗取更多的任务或再次尝试terminate,每次重新terminate的时候,这个数值就会增加。
- GC Worker Other:垃圾收集线程在完成其他任务的时间
- GC Worker Total:展示每个垃圾收集线程的最小、最大、平均、差值和总共时间。
- GC Worker End:min表示最早结束的垃圾收集线程结束时该JVM启动后的时间;max表示最晚结束的垃圾收集线程结束时该JVM启动后的时间。理想情况下,你希望它们快速结束,并且最好是同一时间结束。
- 列出了新生代GC中的一些任务:
- Code Root Fixup :释放用于管理并行垃圾收集活动的数据结构,应该接近于0,该步骤是线性执行的;
- Code Root Purge:清理更多的数据结构,应该很快,耗时接近于0,也是线性执行。
- Clear CT:清理card table
- 包含一些扩展功能
- Choose CSet:选择要进行回收的分区放入CSet(G1选择的标准是垃圾最多的分区优先,也就是存活对象率最低的分区优先)
- Ref Proc:处理Java中的各种引用——soft、weak、final、phantom、JNI等等。
- Ref Enq:遍历所有的引用,将不能回收的放入pending列表
- Redirty Card:在回收过程中被修改的card将会被重置为dirty
- Humongous Register:JDK8u60提供了一个特性,巨型对象可以在新生代收集的时候被回收——通过
G1ReclaimDeadHumongousObjectsAtYoungGC
设置,默认为true。 - Humongous Reclaim:做下列任务的时间:确保巨型对象可以被回收、释放该巨型对象所占的分区,重置分区类型,并将分区还到free列表,并且更新空闲空间大小。
- Free CSet:将要释放的分区还回到free列表。
- 展示了不同代的大小变化,以及堆大小的自适应调整。
- Eden:1097.0M(1097.0M)->0.0B(967.0M):(1)当前新生代收集触发的原因是Eden空间满了,分配了1097M,使用了1097M;(2)所有的Eden分区都被疏散处理了,在新生代结束后Eden分区的使用大小成为了0.0B;(3)Eden分区的大小缩小为967.0M
- Survivors:13.0M->139.0M:由于年轻代分区的回收处理,survivor的空间从13.0M涨到139.0M;
- Heap:1694.4M(2048.0M)->736.3M(2048.0M):(1)在本次垃圾收集活动开始的时候,堆空间整体使用量是1694.4M,堆空间的最大值是2048M;(2)在本次垃圾收集结束后,堆空间的使用量是763.4M,最大值保持不变。
- 第6点展示了本次新生代垃圾收集的时间
- user=0.8:垃圾收集线程在新生代垃圾收集过程中消耗的CPU时间,这个时间跟垃圾收集线程的个数有关,可能会比real time大很多;
- sys=0.0:内核态线程消耗的CPU时间
-real=0.03:本次垃圾收集真正消耗的时间;
并发垃圾收集
G1的第二种收集活动是并发垃圾收集,并发垃圾收集的触发条件有很多,但是做的工作都相同,它的日志如下图所示:
- 标志着并发垃圾收集阶段的开始:
- GC pause(G1 Evacuation Pause)(young)(initial-mark):为了充分利用STW的机会来trace所有可达(存活)的对象,initial-mark阶段是作为新生代垃圾收集中的一部分存在的(搭便车)。initial-mark设置了两个TAMS(top-at-mark-start)变量,用来区分存活的对象和在并发标记阶段新分配的对象。在TAMS之前的所有对象,在当前周期内都会被视作存活的。
- 表示第并发标记阶段做的第一个事情:根分区扫描
- GC concurrent-root-region-scan-start:根分区扫描开始,根分区扫描主要扫描的是新的survivor分区,找到这些分区内的对象指向当前分区的引用,如果发现有引用,则做个记录;
- GC concurrent-root-region-scan-end:根分区扫描结束,耗时0.0030613s
- 表示并发标记阶段
- GC Concurrent-mark-start:并发标记阶段开始。(1)并发标记阶段的线程是跟应用线程一起运行的,不会STW,所以称为并发;并发标记阶段的垃圾收集线程,默认值是Parallel Thread个数的25%,这个值也可以用参数
-XX:ConcGCThreads
设置;(2)trace整个堆,并使用位图标记所有存活的对象,因为在top TAMS之前的对象是隐式存活的,所以这里只需要标记出那些在top TAMS之后、阈值之前的;(3)记录在并发标记阶段的变更,G1这里使用了SATB算法,该算法要求在垃圾收集开始的时候给堆做一个快照,在垃圾收集过程中这个快照是不变的,但实际上肯定有些对象的引用会发生变化,这时候G1使用了pre-write barrier记录这种变更,并将这个记录存放在一个SATB缓冲区中,如果该缓冲区满了就会将它加入到一个全局的缓冲区,同时G1有一个线程在并行得处理这个全局缓冲区;(4)在并发标记过程中,会记录每个分区的存活对象占整个分区的大小的比率; - GC Concurrent-mark-end:并发标记阶段结束,耗时0.3055438s
- GC Concurrent-mark-start:并发标记阶段开始。(1)并发标记阶段的线程是跟应用线程一起运行的,不会STW,所以称为并发;并发标记阶段的垃圾收集线程,默认值是Parallel Thread个数的25%,这个值也可以用参数
- 重新标记阶段,会Stop the World
- Finalize Marking:Finalizer列表里的Finalizer对象处理,耗时0.0014099s;
- GC ref-proc:引用(soft、weak、final、phantom、JNI等等)处理,耗时0.0000480s;
- Unloading:类卸载,耗时0.0025840s;
- 除了前面这几个事情,这个阶段最关键的结果是:绘制出当前并发周期中整个堆的最后面貌,剩余的SATB缓冲区会在这里被处理,所有存活的对象都会被标记;
- 清理阶段,也会Stop the World
- 计算出最后存活的对象:标记出initial-mark阶段后分配的对象;标记出至少有一个存活对象的分区;
- 为下一个并发标记阶段做准备,previous和next位图会被清理;
- 没有存活对象的老年代分区和巨型对象分区会被释放和清理;
- 处理没有任何存活对象的分区的RSet;
- 所有的老年代分区会按照自己的存活率(存活对象占整个分区大小的比例)进行排序,为后面的CSet选择过程做准备;
- 并发清理阶段
- GC concurrent-cleanup-start:并发清理阶段启动。完成第5步剩余的清理工作;将完全清理好的分区加入到二级free列表,等待最终还会到总体的free列表;
- GC concurrent-cleanup-end:并发清理阶段结束,耗时0.0012954s
混合收集
在并发收集阶段结束后,你会看到混合收集阶段的日志,如下图所示,该日志的大部分跟之前讨论的新生代收集相同,只有第1部分不一样:GC pause(G1 Evacuation Pause)(mixed),0.0129474s,这一行表示这是一个混合垃圾收集周期;在混合垃圾收集处理的CSet不仅包括新生代的分区,还包括老年代分区——也就是并发标记阶段标记出来的那些老年代分区。
Full GC
如果堆内存空间不足以分配新的对象,或者是Metasapce空间使用率达到了设定的阈值,那么就会触发Full GC——你在使用G1的时候应该尽量避免这种情况发生,因为G1的Full Gc是单线程、会Stop The World,代价非常高。Full GC的日志如下图所示,从中你可以看出三类信息
- Full GC的原因,这个图里是Allocation Failure,还有一个常见的原因是Metadata GC Threshold;
- Full GC发生的频率,每隔几天发生一次Full GC还可以接受,但是每隔1小时发生一次Full GC则不可接受;
- Full GC的耗时,这张图里的Full GC耗时150ms(PS:按照我的经验,实际运行中如果发生Full GC,耗时会比这个多很多)
基础配置参数中,我这里还想介绍两个:-XX:+PrintGCApplicationStoppedTime
和-XX:+PrintGCApplicationConcurrentTime
,这两个参数也可以为你提供有用的信息,如下图所示:
- 记录了应用线程在安全点被暂停的总时间(也就是STW的总时间)
- 记录了让所有应用线程进入安全点所花费的总时间
- 记录了在两个安全点之间应用线程运行的时间
总结
这篇文章只是翻译了原文的第一部分,基础参数篇,我接下来会有一篇或两篇文章完成原文的高级参数和Debug参数两部分。------
本号(javaadu)专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
【译】深入理解G1的GC日志(一)的更多相关文章
- 理解Java的GC日志
分析如下GC日志:[GC [PSYoungGen: 9216K->1024K(9216K)] 1246196K->1246220K(1287040K), 0.2398360 secs] [ ...
- 理解G1垃圾回收日志
本篇文章主要介绍在-XX:+PrintGCDetails选项的情况下G1 GC log打印的信息.(注:本文假设读者对G1算法的基本原理已经有所了解) 下面是一段G1垃圾收集器相关的log信息 0.5 ...
- 曹工杂谈:手把手带你读懂 JVM 的 gc 日志
一.前言 今天下午本来在划水,突然看到微信联系人那一个红点点,看了下,应该是博客园的朋友.加了后,这位朋友问了我一个问题: 问我,这两块有什么关系? 看到这段 gc 日志,一瞬间脑子还有点懵,嗯,这个 ...
- Java垃圾收集器——Parallel、G1收集器日志分析及性能调优示范
开发过程中,经常需要对GC的垃圾收集器参数不断的进行动态调整,从而更充分的压榨机器性能,提升应用效率.本文将从常见的Parallel/G1垃圾收集器的GC日志着手,分析GC日志的具体含义,以及示范如何 ...
- GC垃圾回收 | 深入理解G1垃圾收集器和GC日志
来源:并发编程网链接:http://ifeve.com/深入理解G1垃圾收集器/ G1 GC是Jdk7的新特性之一.Jdk7+版本都可以自主配置G1作为JVM GC选项:作为JVM GC算法的一次重大 ...
- 《深入理解Java虚拟机》(六)堆内存使用分析,垃圾收集器 GC 日志解读
堆内存使用分析,GC 日志解读 重要的东东 在Java中,对象实例都是在堆上创建.一些类信息,常量,静态变量等存储在方法区.堆和方法区都是线程共享的. GC机制是由JVM提供,用来清理需要清除的对象, ...
- 浅谈 G1 GC 日志格式
在 Java9 中,G1 GC 将成为默认的垃圾收集器,G1 垃圾收集器的关键特性之一是能够在不牺牲吞吐量的同时,限制 GC 暂停时间(即可以设置所需的最大停顿时间). 由于 G1 GC 正在逐渐成为 ...
- 【转载】Java垃圾回收内存清理相关(虚拟机书第三章),GC日志的理解,CPU时间、墙钟时间的介绍
主要看<深入理解Java虚拟机> 第三张 P84 开始是垃圾收集相关. 1. 1960年诞生于MIT的Lisp是第一门采用垃圾回收的语言. 2. 程序计数器.虚拟机栈.本地方法栈3个区域随 ...
- 理解CMS GC日志
本文翻译自:https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs 准备工作 JVM的GC日志的主要参数包括如下几个:-XX:+ ...
随机推荐
- 使用Advanced Installer 13.1打包发布 Windows Service服务程序
原文: 使用Advanced Installer 13.1打包发布 Windows Service服务程序 项目中需要用到一个定时推送案件状态的需求,本人小菜一只,在同事建议下要写成一个windows ...
- .NET重思(三)-数组列表与数组的区别,栈集合和队列结合的区别
数组列表和数组十分相似,区别在于数组列表的容量是可以动态变化的,而数组的容量是固定的.数组即Array类,数组列表即ArrayList类,两者十分相似.不过,Array类在System命名空间下,Ar ...
- qt实现-给SQLITE添加自定义函数(对某个字段进行加密)
需要使用sqlite里的password对某个字段进行加密,由于使用的sqlite是由QT封装好的QSqlDatabase,没有发现加载扩展函数的方法,所以自己实现了一个. 在网上也没找到相应的参考, ...
- web.congfig 禁用 ViewState Session
<!--禁用 ViewState Session--> <pages enableViewState="false" enableSessionState=&qu ...
- ZooKeeper学习第四期---构建ZooKeeper应用(转)
转载来源:https://www.cnblogs.com/sunddenly/p/4064992.html 一.配置服务 配置服务是分布式应用所需要的基本服务之一,它使集群中的机器可以共享配置信息中那 ...
- C#中await/async闲说
自从C#5.0增加异步编程之后,异步编程越来越简单,async和await用的地方越来越多,越来越好用,只要用异步的地方都是一连串的异步,如果想要异步编程的时候,需要从底层开始编写,这样后边使用的时候 ...
- Free MP3 CD Ripper_缓冲区溢出远程代码执行_CVE-2019-9766漏洞复现
Free MP3 CD Ripper_缓冲区溢出远程代码执行_CVE-2019-9766漏洞复现 一.漏洞描述 Free MP3 CD Ripper是一款音频格式转换器.Free MP3 CD Rip ...
- Azkaban学习之路(一)—— Azkaban 简介
一.Azkaban 介绍 1.1 背景 一个完整的大数据分析系统,必然由很多任务单元(如数据收集.数据清洗.数据存储.数据分析等)组成,所有的任务单元及其之间的依赖关系组成了复杂的工作流.复杂的工作流 ...
- 【简易bat脚本】启动java程序
前置条件:path中添加了JAVAHOME配置了java环境变量 1.新建txt文本文件 2.粘贴以下内容 @echo off set path=%path%;.;java -classpath &q ...
- Python笔记【2】_列表学习
#!/usr/bin/env/python #-*-coding:utf-8-*- #Author:LingChongShi #查看源码Ctrl+左键 #字符串:通常有单引号“'”.双引号“" ...