什么是G1
G1:Garbage First。顾名思义,第一时间处理垃圾最多的区域(region)。
由此引出了“region”的概念。不同于其他的分代回收算法,G1将堆空间划分成了互相独立的区域(region)。每块区域既有可能属于O区、也有可能是Y区,且每类区域空间可以是不连续的(对比CMS的O区和Y区都必须是连续的)。
这里强调一下,G1仍然采取分代策略,只是新生代、老年代等不要求在物理空间隔离了。
当垃圾回收线程扫描到某一区域内的可回收对象较多时,会优先处理这部分区域。
G1比CMS有哪些改进
相关cms内容参考详解cms垃圾回收机制
1.G1通过将内存空间分成区域(Region)的方式避免内存碎片问题。
2.Eden, Survivor, Old区不再固定、在内存使用效率上来说更灵活。
3.G1可以在新生代中使用,而CMS只能在老年代使用。
4.G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
G1垃圾回收过程
G1可以在新生代、老年代使用,因而回收过程也不同。
新生代过程
G1的新生代收集跟ParNew类似,当新生代占用达到一定比例的时候,开始出发收集。
G1中的新生代
新生代对象在绿色区域,老年代对象在蓝色区域,灰色是没有被使用的区域。
G1中的新生代垃圾回收
存活的对象移入到surviour区域,若某些对象年龄达到了设置的阈值,则会移入到老年代。
新生代的垃圾回收需要stop the world,同时会根据统计信息调整eden区、surviour区大小。
G1中新生代垃圾回收之后
新生代垃圾回收结束后将存活对象移入到surviour区
新生代的垃圾回收策略总结如下:
-
堆被分为独立的region。
-
新生代由不连续的region组成,方便调整大小。
-
新生代垃回收需要stop the world。
-
新生代垃圾回收由多个线程并发完成。
-
存活的对象移入到surviour区或老年代。
可以看出新生代的策略只是在内存区域划分上有所不同,其他的和parnew是一样的。
老年代过程(mixed gc)
mixed gc:选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
初始标记
此阶段需要stop the world。这一阶段会触发新生代垃圾回收。标记可能引用了老年代对象的survivor区域。在日志中以 GC pause (young)(inital-mark)标识。
根区域扫描
扫描引用了老年代的survivor区域。此阶段和用户线程并发执行。并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。(可以理解为初始标记的延续,不过是并发执行的)
并发标记
在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会在Remark阶段被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
重标记
会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
空的区域被移除并回收。并计算所有区域的活跃度(Region liveness).
Copy/Clean up
多线程清除失活对象,G1选择“活跃度(liveness)”最低的区域, 这些区域可以最快的完成回收,会有STW。
G1将回收区域的存活对象拷贝到新区域,清除Remember Sets。
这一过程与新生代垃圾回收同时发生,在gc日志中以 [GC pause (mixed)]标识。
复制/清除过程后
回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。
大对象处理
在G1中,对于超过0.5个region size的对象是大对象,对于大对象不会移动,只会清理,并且直接分配到old中。
关于Remembered Set概念
在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。
但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描。由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可。
需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将堆在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。
一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。
逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)
上图中有三个Region,每个Region被分成了多个Card,在不同Region中的Card会相互引用,Region1中的Card中的对象引用了Region2中的Card中的对象,蓝色实线表示的就是points-out的关系,而在Region2的RSet中,记录了Region1的Card,即红色虚线表示的关系,这就是points-into。
关于Collection Sets概念
简称 CSets, 收集集合, 在一次GC中将执行垃圾回收的heap区. GC时在CSet中的所有存活数据(live data)都会被转移(复制/移动). 集合中的heap区可以是 Eden, survivor, 和/或 old generation. CSets所占用的JVM内存小于1%.
SATB概念
老年代GC(Old Generation GC)总结
总结下来,G1对老年代的GC有如下几个关键点:
-
并发标记清理阶段(Concurrent Marking Phase)
-
活跃度信息在程序运行的时候被并行计算出来
-
活跃度(liveness)信息标识出哪些区域在转移暂停期间最适合回收.
-
不像CMS一样有清理阶段(sweeping phase).
-
-
再次标记阶段(Remark Phase)
-
使用的 Snapshot-at-the-Beginning (SATB, 开始快照) 算法比起 CMS所用的算法要快得多.
-
完全空的区域直接被回收.
-
-
拷贝/清理阶段(Copying/Cleanup Phase)
-
年轻代与老年代同时进行回收.
-
老年代的选择基于其活跃度(liveness).
-
G1参数列表
选项/默认值 | 说明 |
---|---|
-XX:+UseG1GC | 使用 G1 (Garbage First) 垃圾收集器 |
-XX:MaxGCPauseMillis=n | 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标. |
-XX:InitiatingHeapOccupancyPercent=n | 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45. |
-XX:NewRatio=n | 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2. |
-XX:SurvivorRatio=n | eden/survivor 空间大小的比例(Ratio). 默认值为 8. |
-XX:MaxTenuringThreshold=n | 提升年老代的最大临界值(tenuring threshold). 默认值为 15. |
-XX:ParallelGCThreads=n | 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同. |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同. |
-XX:G1ReservePercent=n | 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10. |
-XX:G1HeapRegionSize=n | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb. |
G1调优建议
不设置young大小
假若通过 -Xmn 显式地指定了年轻代的大小, 则会干扰到 G1收集器的默认行为.
-
G1在垃圾收集时将不再关心暂停时间指标. 所以从本质上说,设置年轻代的大小将禁用暂停时间目标.
-
G1在必要时也不能够增加或者缩小年轻代的空间. 因为大小是固定的,所以对更改大小无能为力.
什么是转移失败(Evacuation Failure)
对 survivors 或 promoted objects 进行GC时如果JVM的heap区不足就会发生提升失败(promotion failure). 堆内存不能继续扩充,因为已经达到最大值了. 当使用 -XX:+PrintGCDetails
时将会在GC日志中显 示 to-space overflow (to-空间溢出)。
这是很昂贵的操作!
-
GC仍继续所以空间必须被释放.
-
拷贝失败的对象必须被放到正确的位置(tenured in place).
-
CSet指向区域中的任何 RSets 更新都必须重新生成(regenerated).
-
所有这些步骤都是代价高昂的.
如何避免转移失败(Evacuation Failure)
要避免避免转移失败, 考虑采纳下列选项.
-
增加堆内存大小
-
增加 -XX:G1ReservePercent=n, 其默认值是 10.
-
G1创建了一个假天花板(false ceiling),在需要更大 'to-space' 的情况下会尝试从保留内存获取(leave the reserve memory free).
-
-
更早启动标记周期(marking cycle)
-
通过采用 -XX:ConcGCThreads=n 选项增加标记线程(marking threads)的数量.
响应时间指标(Response Time Metrics)
设置 XX:MaxGCPauseMillis=<N>
时不应该使用平均响应时间(ART, average response time) 作为指标,而应该考虑使用目标时间的90%或者更大作为响应时间指标. 也就是说90%的用户(客户端/?)请求响应时间不会超过预设的目标值. 记住,暂停时间只是一个目标,并不能保证总是得到满足.
通过各种参数控制mixed GC
G1 GC 是区域化、并行-并发、增量式垃圾回收器,相比其他 HotSpot 垃圾回收器,可提供更多可预测的暂停。增量的特性使 G1 GC 适用于更大的堆,在最坏的情况下仍能提供不错的响应。G1 GC 的自适应特性使 JVM 命令行只需要软实时暂停时间目标的最大值以及 Java 堆大小的最大值和最小值,即可开始工作
如何查看G1日志
设置日志细节(Log Detail)
可以设置3种不同的日志级别.
verbosegc
等价于 -XX:+PrintGC,设置日志级别为 好 fine.
日志输出示例
[GC pause (G1 Humongous Allocation) (young) (initial-mark) 24M- >21M(64M), 0.2349730 secs]
[GC pause (G1 Evacuation Pause) (mixed) 66M->21M(236M), 0.1625268 secs]
XX:+PrintGCDetails
设置日志级别为 更好 finer. 使用此选项会显示以下信息:
-
每个阶段的 Average, Min, 以及 Max 时间.
-
根扫描(Root Scan), RSet 更新(同时处理缓冲区信息), RSet扫描(Scan), 对象拷贝(Object Copy), 终止(Termination, 包括尝试次数).
-
还显示 “other” 执行时间, 比如选择 CSet, 引用处理(reference processing), 引用排队(reference enqueuing) 以及释放(freeing) CSet等.
-
显示 Eden, Survivors 以及总的 Heap 占用信息(occupancies).
日志输出示例
[Ext Root Scanning (ms): Avg: 1.7 Min: 0.0 Max: 3.7 Diff: 3.7]
[Eden: 818M(818M)->0B(714M) Survivors: 0B->104M Heap: 836M(4096M)->409M(4096M)]
XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest
设置日志级别为 最好 finest. 和 finer 级别类似, 包含每个 worker 线程信息.
[Ext Root Scanning (ms): 2.1 2.4 2.0 0.0 Avg: 1.6 Min: 0.0 Max: 2.4 Diff: 2.3]
[Update RS (ms): 0.4 0.2 0.4 0.0 Avg: 0.2 Min: 0.0 Max: 0.4 Diff: 0.4]
[Processed Buffers : 5 1 10 0 Sum: 16, Avg: 4, Min: 0, Max: 10, Diff: 10]
Determining Time
有两个参数决定了GC日志中打印的时间显示形式.
XX:+PrintGCTimeStamps
显示从JVM启动时算起的运行时间.
日志输出示例
1.729: [GC pause (young) 46M->35M(1332M), 0.0310029 secs]
XX:+PrintGCDateStamps
在每条记录前加上日期时间.
日志输出示例
2012-05-02T11:16:32.057+0200: [GC pause (young) 46M->35M(1332M), 0.0317225 secs]
理解G1日志
G1日志官方说明: G1垃圾回收日志 .
G1日志相关术语
-
Clear CT
-
CSet
-
External Root Scanning
-
Free CSet
-
GC Worker End
-
GC Worker Other
-
Object Copy
-
Other
-
Parallel Time
-
Ref Eng
-
Ref Proc
-
Scanning Remembered Sets
-
Termination Time
-
Update Remembered Set
-
Worker Start
Parallel Time(并行阶段耗时)
414.557: [GC pause (young), 0.03039600 secs] [Parallel Time: 22.9 ms]
[GC Worker Start (ms): 7096.0 7096.0 7096.1 7096.1 706.1 7096.1 7096.1 7096.1 7096.2 7096.2 7096.2 7096.2
Avg: 7096.1, Min: 7096.0, Max: 7096.2, Diff: 0.2]
Parallel Time
– 主要并行部分运行停顿的整体时间
Worker Start
– 各个工作线程(workers)启动时的时间戳(Timestamp)
Note: 日志是根据 thread id 排序,并且每条记录都是一致的.
External Root Scanning(外部根扫描)
[Ext Root Scanning (ms): 3.1 3.4 3.4 3.0 4.2 2.0 3.6 3.2 3.4 7.7 3.7 4.4
Avg: 3.8, Min: 2.0, Max: 7.7, Diff: 5.7]
External root scanning
- 扫描外部根花费的时间(如指向堆内存的系统词典(system dictionary)等部分)
Update Remembered Set(更新 RSet)
[Update RS (ms): 0.1 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 Avg: 0.0, Min: 0.0, Max: 0.1, Diff: 0.1]
[Processed Buffers : 26 0 0 0 0 0 0 0 0 0 0 0 Sum: 26, Avg: 2, Min: 0, Max: 26, Diff: 26]
Update Remembered Set
- 必须更新在pause之前已经完成但尚未处理的缓冲. 花费的时间取决于cards的密度。cards越多,耗费的时间就越长。
Scanning Remembered Sets(扫描 RSets)
[Scan RS (ms): 0.4 0.2 0.1 0.3 0.0 0.0 0.1 0.2 0.0 0.1 0.0 0.0 Avg: 0.1, Min: 0.0, Max: 0.4, Diff: 0.3]F
Scanning Remembered Sets
- 查找指向 Collection Set 的指针(pointers)
Object Copy(对象拷贝)
[Object Copy (ms): 16.7 16.7 16.7 16.9 16.0 18.1 16.5 16.8 16.7 12.3 16.4 15.7 Avg: 16.3, Min: 12.3, Max: 18.1, Diff: 5.8]
Object copy
– 每个独立的线程在拷贝和转移对象时所消耗的时间.
Termination Time(结束时间)
[Termination (ms): 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 Avg: 0.0, Min: 0.0, Max: 0.0, Diff: 0.0] [Termination Attempts : 1 1 1 1 1 1 1 1 1 1 1 1 Sum: 12, Avg: 1, Min: 1, Max: 1, Diff: 0]
Termination time
- 当worker线程完成了自己那部分对象的复制和扫描,就进入终止协议(termination protocol)。它查找未完成的工作(looks for work to steal), 一旦它完成就会再进入终止协议。 终止尝试记录(Termination attempt counts)所有查找工作的尝试次数(attempts to steal work) .
GC Worker End
[GC Worker End (ms): 7116.4 7116.3 7116.4 7116.3 7116.4 7116.3 7116.4 7116.4 7116.4 7116.4 7116.3 7116.3 Avg: 7116.4, Min: 7116.3, Max: 7116.4, Diff: 0.1]
[GC Worker (ms): 20.4 20.3 20.3 20.2 20.3 20.2 20.2 20.2 20.3 20.2 20.1 20.1 Avg: 20.2, Min: 20.1, Max: 20.4, Diff: 0.3]
GC worker end time
– 独立的 GC worker 停止时的时间戳.
GC worker time
– 每个独立的 GC worker 线程消耗的时间.
GC Worker Other
[GC Worker Other (ms): 2.6 2.6 2.7 2.7 2.7 2.7 2.7 2.8 2.8 2.8 2.8 2.8 Avg: 2.7, Min: 2.6, Max: 2.8, Diff: 0.2]
GC worker other
– 每个GC线程中不能归属到之前列出的worker阶段的其他时间. 这个值应该很低. 过去我们见过很高的值,是由于JVM的其他部分的瓶颈引起的(例如在分层[Tiered]代码缓存[Code Cache]占有率的增加)。
Clear CT
[Clear CT: 0.6 ms]
清除 RSet 扫描元数据(scanning meta-data)的 card table 消耗的时间.
Other
[Other: 6.8 ms]
其他各种GC暂停的连续阶段花费的时间.
CSet
[Choose CSet: 0.1 ms]
敲定要进行垃圾回收的region集合时消耗的时间. 通常很小,在必须选择 old 区时会稍微长一点点.
Ref Proc
[Ref Proc: 4.4 ms]
处理 soft, weak, 等引用所花费的时间,不同于前面的GC阶段
Ref Enq
[Ref Enq: 0.1 ms]
将 soft, weak, 等引用放置到待处理列表(pending list)花费的时间.
Free CSet
[Free CSet: 2.0 ms]
释放刚被垃圾收集的 heap区所消耗的时间,包括对应的remembered sets。
待解决问题
1.marking cycle如何触发
参考资料
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
http://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html
http://blog.youkuaiyun.com/renfufei/article/details/41897113
https://blogs.oracle.com/poonam/entry/understanding_g1_gc_logs