java g1 gc ref proc_深刻理解垃圾收集器的G1及日志分析

尽管Hotspot 最新的垃圾回收器G1是在2006年推出的。可是G1从推行至今的市场反响来看,但如今足以证实这款垃圾收集器是经得起考验的,从java9开始,就默认为G1垃圾收集器。G1是一款面向服务端应用的垃圾收集器。HotSpot开发团队赋予它的使命是(在比较长期的)将来能够替换掉JDK1.5中发布的CMS收集器。与其余GC收集器相比,G1具有以下特色。java

并行与并发、分代收集的垃圾收集算法、可预测的停顿、空间整合。算法

特色

从分代来看,G1依然属于分代垃圾收集器,她会区分年轻代和老年代,依然有eden区和survivor区,但从堆的结构来看,它并不要求整个eden、年轻代或者老年代都连续,它使用了分区算法。bash

并行性: 在回收期间,能够由多个GC线程同时工做,有效利用多核计算能力。并发

井发性: GI 拥有与应用程序交替执行的能力,部分工做能够和应用程序同时执行,所以一 般来讲,不会在整个回收期间彻底阻塞应用程序。性能

分代 GC : Gl 依然是一个分代收集器,可是和以前回收器不一样,它同时兼顾年轻代和老年代。对比其余回收器,它们或者工做在年轻代,或者工做在老年代。所以,这里是一个很大的不一样。spa

空间整理: Gl 在回收过程当中,会进行适当的对象移动,不像CMS,只是简单地标记清理对象,在若干次 GC 后,CMS 必须进行一次碎片整理。而Gl不一样,它每次回收都会有效地复制对象,减小空间碎片。.net

G1把内存“化整为零”的思路,理解起来彷佛很容易,但其中的实现细节却远远没有想象中那样简单,不然也不会从2004年Sun实验室发表第一篇G1的论文开始直到今天(将近10年时间)才开发出G1的商用版。线程

笔者以一个细节为例:把Java堆分为多个Region后,垃圾收集是否就真的能以Region为单位进行了?听起来瓜熟蒂落,再仔细想一想就很容易发现问题所在:Region不多是孤立的。一个对象分配在某个Region中,它并不是只能被本Region中的其余对象引用,而是能够与整个Java堆任意的对象发生引用关系。那在作可达性断定肯定对象是否存活的时候,岂不是还得扫描整个Java堆才能保证准确性?这个问题其实并不是在G1中才有,只是在G1中更加突出而已。在之前的分代收集中,新生代的规模通常都比老年代要小许多,新生代的收集也比老年代要频繁许多,那回收新生代中的对象时也面临相同的问题,若是回收新生代时也不得不一样时扫描老年代的话,那么Minor GC的效率可能降低很多。3d

​ 在G1收集器中,Region之间的对象引用以及其余收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每一个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操做时,会产生一个Write Barrier暂时中断写操做,检查Reference引用的对象是否处于不一样的Region之中(在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象),若是是,便经过CardTable把相关引用信息记录到被引用对象所属的Region的RememberedSet之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set便可保证不对全堆扫描也不会有遗漏。日志

G1的内存划分和主要收集过程

G1收集器讲堆进行分区,划分为一个个区域,每次收集时,只收集其中几个区域,以此来控制垃圾回收产生的一次停顿时间。

G1收集过程四个阶段:

新生代GC(YGC) 并发标记周期 混合收集 若是须要进行full GC

G1的新生代GC

新生代GC的主要工做是回收eden区和survivor区。 一旦eden 区被占满,新生代GC就会启动。新生代GC收集先后的堆数据如图5.6所示,其中E表示eden区,S表示survivor区, o表示老年代。能够看到,新生代GC只处理eden和survivor区,回收后,全部的eden区都应该被消空,而survivor区会被收集一 部分数据,可是应该至少仍然存在一 个 survivor 区,类比其余的新生代收集器,这一 点彷佛并无太大变化。另外一 个亟要的变化是老年代的区域增多,由于部分survivor区或者eden区的对象可能会晋升到老年代。

f6be2cfc74e940932c8f492a257a9660.png

c38ade774b58619a366cb335edf4d15c.png 从日志中能够看到,eden区本来占用235MB空间,回收后被清空,survivor区从5MB增加到了11MB, 这是由于部分对象从eden区复制到survivor区,整个堆合计为400MB, 从回收前的239MB降低到10.5MB。

G1的井发标记周期

G1的并发阶段与CMS有点相似,他们都是为了下降一次停顿时间,而将能够和应用程序并发的部分单独提取出来执行。

并发标记周期能够分为如下几步。若是不计算维护RememberedSet的操做,G1收集器的运做大体可划分为如下几个步骤:

初始标记(Initial Marking)

根区域扫描

并发标记(Concurrent Marking)

最终标记(Final Marking)

独占清理

筛选回收(Live Data Counting and Evacuation)

复制代码

初始标记: 标记从根节点直接可达的对象。这个阶段会伴随一次新生代GC,它会产生全局停顿。

根区域扫描: 因为初始标记必然会伴随一次新生代的GC,因此在初始化标记后,eden被清空,而且存活对象被移入survivor区。这个阶段,将扫面survivor区直接可达的老年代对象,并标记这些直接可达的对象。根区域扫描不能和新生代GC同时执行。

并发标记: 和CMS相似,扫面查找整个对存活的对象,这是一个并发的过程,能够被一次新生代GC打断。

从新标记: 因为并发标记过程当中,应用仍在执行,所以标记结果须要修正,因此对上一次的标记结果进行补充,在G1中,这个过程使用STAB算法完成。即G1会在标记之初为存活对象建立一个快照,有助于加速从新标记速度。

独占清理: 这个阶段会引发停顿。

并发清理阶段: 识别并清理彻底空闲的区域。它是并发的清理,不会引发停顿。

3505cc04e4ec74e9bcc2fb43ff3bcd3e.png

9e73e8c90fff874ffd1a07961cccb453.png

在并发标记周期时,G1会产生以下日志:

(1)、初始标记,伴随一次新生代GC

[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0117414 secs]

...

[Eden: 1024.0K(4096.0K)->0.0B(2048.0K) Survivors: 2048.0K->1024.0K Heap: 9581.8K(20.0M)->12.3M(20.0M)]

[Times: user=0.11 sys=0.00, real=0.01 secs]

复制代码

能够看到初始化标记时,eden被清空,并部分复制到survivor区

(2)、这是一次并发的根区域扫描,并发扫面过程当中,不能被新生代GC打断。

[GC concurrent-root-region-scan-start]

[GC concurrent-root-region-scan-end, 0.0007368 secs]

复制代码

(3)、下面这个是指并发标记

[GC concurrent-mark-start]

[GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 0.0427113 secs]

.....

[Eden: 2048.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 16.4M(20.0M)->18.0M(20.0M)]

[Times: user=0.05 sys=0.02, real=0.04 secs]

...

[GC concurrent-mark-abort]

复制代码

(4)、从新标记引发全局停顿

[Ref Proc: 0.3 ms]

复制代码

(5)、从新标记后进行独占清理

4.088: [GC cleanup 117M->106M(138M), 0.0015198 secs]

复制代码

(6)、并发清理是并发执行的,会根据独占清理阶段计算出的每一个区域的存活对象数量,直接回收已经不包含存活对象的区域。

4.090: [GC concurrent-cleanup-start]

并发清理阶段开始,它释放被发现为空的区域(不包含任何的活跃数据的区域),在上一个stop-the-world阶段期间。

4.091: [GC concurrent-cleanup-end, 0.0002721]

并发清理阶段清理空的区域用时0.0002721秒。

复制代码

关于G1日志,想要知道全部内容的,能够看这篇文章,适合查询: blog.csdn.net/zhanggang80…

混合回收

在并发标记周期中,虽然有部分对象被回收,可是从总体上来讲,回收的比例是至关低的。可是在并发标记周期后,G1已经明确知道哪些区域含有比较多的垃圾对象了,在混合阶段,就是对这些区域进行回收的。固然,会优先回收垃圾比例比较高的区域。由于回收这些区域的性价比比较高。

G1是指垃圾优先的垃圾回收器,全称"Garbage First Garbage Collector".

在混合回收中,即会执行正常年轻代GC,也会选取被标记的老年代区域进行回收,它同时处理了新生代和老年代。由于新生代GC的缘由,eden区域必然被清空,此外,以下图,有两块区域被标记为G的垃圾回收比例最高的区域被清理。被清理区域的存活对象会被移动到其余区域,这样的好处是能够减小空间碎片。

f732e57dfe70393ad4b9c2ac8d3ed81c.png

混合GC产生以下日志:

7c4dce1ba788f5250c9a56a65325d9dc.png 混合GC执行屡次,直到回收了足够多的内存空间,触发一次新生代GC。新生代GC后,有可能会发生一次并发标记周期的清理,最后又引发混合GC。整个流程见下图:

3b29968bd593a147fe93044001c6dd78.png

必要时的Full GC

与CMS相似,并发收集因为让应用和GC线程交替工做,所以老是不能避免在特别的繁忙场合在回收过程致使内存不足,此时,G1也会执行一个Full GC回收。

d53e9ab6ab47a4a53896c2f925abd699.png 此外,在混合GC和新生代GC时,survivor与老年代没法没法容纳幸存对象,都会致使Full GC产生

完整的G1日志分析

56e4a6dea280d4859579f20c0c576af8.png

4d3ad712ec3e761ffd70577a9dc3a723.png

44b1c8a589b1f898aff1460c0cc11333.png

ccc94adb40f5d9382a37bfe73dcc8d4c.png

3c27db9a59cb1771998bbc2ecae5b2e5.png

ec673f2ba55eeb86687a7e5573252200.png

G1 的相关参数

对于Gl收集器,可使用-XX:+UseGIGC标记打开Gl收集器开关,对Gt收集器进行设置时,最重要的一 个参数就是-XX :MaxGCPauseMillis,它用于指定目标最大停顿时间。若是任何一次停顿超过这个设置值时,Gl就会尝试调整新生代和老年代的比例、调整堆大小、调整晋升年龄等手段,试图达到预设目标。对于性能调优来讲,

有时候,老是鱼和熊掌不可兼得的,若是停顿时间缩短,对千新生代来讲,这意味着极可能要增长新生代GC的次数,GC反而会变得更加频繁。对于老年代区域来讲,为了得到更短的停顿时间,那么在混合GC收集时,一次收集的区域数量也会变少,这样无疑增长了进行FullGC的可能性。另一个重要的参数是-XX :ParallelGCThreads, 它用于设置并行回收时,GC的工做线程数量。

此外,-XX:InitiatingHeapOccupancyPercent参数能够指定当整个堆使用率达到多少时,触发并发标记周期的执行。默认值是45, 即当整个堆占用率达到45%时,执行并发标记周期。 InitiatingHeapOccupancyPercent 一 旦设置,始终都不会被G l收集器修改,这意味着G I收集器不会试图改变这个值,来知足MaxGCPause汕His的目标。若是InitiatingHeapOccupancyPercent值设置偏大,会致使并发周期迟迟得不到启动,那么引发Full GC的可能性也大大增长,反之,一 个太小的 InitiatingHeapOccupancyPercent值,会使得并发周期很是频繁,大整 GC 线程抢占CPU, 会致使应用程序的性能有所降低。

来自《深刻理解JVM虚拟机》JVM高级特性与最佳实现。

《实战java虚拟机》复制代码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值