深入理解G1垃圾收集器

G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术,JDK9 默认就是使用的G1垃圾收集器。

不同于其他的分代回收算法,G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。每块区域既有可能属于O区、也有可能是Y区,且每类区域空间可以是不连续的(对比CMS的O区和Y区都必须是连续的)。

G1有三个明显特点:1、 压 缩 空 间 强 , 避 免 碎 片 \color{red}{压缩空间强,避免碎片} 2、空间使用更灵活 3、 G C 停 顿 周 期 更 可 控 , 避 免 雪 崩 \color{red}{GC停顿周期更可控, 避免雪崩} GC

G1其实是Garbage First的意思,垃圾优先? 不是,是优先处理那些垃圾多的内存块的意思。

一般的垃圾回收器把内存分成三类: Eden(E), Suvivor(S)和Old(O)。
在这里插入图片描述
G1虽然也把内存分成了这三大类,但是在G1里面这三大类不是泾渭分明的三大块内存,G1把内存划分成很多小块, 每个小块会被标记为E/S/O中的一个,可以前面一个是Eden后面一个就变成Survivor了。
在这里插入图片描述
这么做给G1带来了很大的好处,由于把三块内存变成了几百块内存,内存块的粒度变小了,从而可以垃圾回收工作更彻底的并行化

G1的并行收集做得特别好,我们第一次听到并行收集应该是CMS(Concurrent Mark & Sweep)垃圾回收算法, 但是CMS的并行收集也只是在收集老年代能够起效,而在回收年轻代的时候CMS是要暂停整个应用的(Stop-the-world)。而G1整个收集全程几乎都是并行的

它回收的大致过程是这样的:

  1. 在垃圾回收的最开始有个短暂的时间段(Inital Mark)会停止应用(stop-the-world)
  2. 然后应用继续运用,同时G1开始Concurrent Mark
  3. 再次停止应用,来Final Mark(stop-the-world)
  4. 最后感觉Garbage First的原则,选择一些内存块进行回收

G1的另一个显著特点他能够让用户设置应用的暂停时间,为什么G1能做到这一点呢?

也许你已经注意到了,G1回收的第4步,它是“选择一些内存块”,而不是整代内存来回收,这是G1跟其它GC非常不同的一点,其它GC每次回收都会回收整个Generation的内存(Eden, Old), 而回收内存所需的时间就取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存, 到 底 回 收 多 少 内 存 就 看 用 户 配 置 的 暂 停 时 间 \color{red}{到底回收多少内存就看用户配置的暂停时间} ,配置的时间短就少回收点,配置的时间长就多回收点 ,伸缩自如。

由于内存被分成了很多小块,又带来了另外好处,由于内存块比较小,进行内存压缩整理的代价都比较小,相比其它GC算法,可以有效的规避内存碎片的问题。

记忆集Remembered Set:

G1相关概念非常多,有一个重点就是Remembered Set,用于记录和维护region之间对象的引用关系。为什么需要这么做呢?试想,新生代GC是复制算法,也就是说,类似对象 从Eden或者Survivor到to区域的“移动”,其实是“复制”,本质上是一个新的对象。在这个过程中,需要必须保证老年代到新生代的跨区引用仍然有效。比如card table是remembered set的一种实现。下面的示意图说明了相关设 计。
在这里插入图片描述

G1 的缺点:

G1 需要记忆集 (具体来说是卡表,Remembered Set)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。

G1对比CMS的区别在:

  1. G1在压缩空间方面有优势
  2. G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
  3. Eden,Survivor,Old区不再固定、在内存使用效率上来说更灵活
  4. G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象,可驾驭度,G1 是可以设定GC 暂停的 target 时间的,根据预测模型选取性价比收益更高,且一定数目的 Region 作为 CSet,能回收多少便是多少。
  5. G1在回收内存后会马上同时做,合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做
  6. G1会在Young GC中使用、而CMS只能在O区使用
  7. SATB 算法在 remark 阶段延迟极低以及借助 RSet 的实现可以不做全堆扫描(G1 对大堆更友好)以外,最重要的是可驾驭度

目前CMS还是默认首选的GC策略、可能在以下场景下G1更适合:

  1. 服务端多核CPU、JVM内存占用较大的应用(至少大于4G)
  2. 应用在运行过程中产生大量内存碎片、需要经常压缩空间
  3. 想要更可控、可预期的GC停顿周期:防止高并发应用雪崩现象

G1 的 JVM调优

1、region太小不合适,会令你在分配大对象时更难找到连续空间
-XX:G1HeapRegionSize=<N, 例如16>M
2、老年代回收,则是依靠Mixed GC,指定触发阈值,并且设定最多被包含在一次Mixed GC中的region比例
–XX:G1MixedGCLiveThresholdPercent –XX:G1OldCSetRegionThresholdPercent
3、在垃圾收集过程中,G1会把新创建的字符串对象放入队列中,然后在Young GC之后,并发地(不会STW)将内部数据 (char数组,JDK 9以后是byte数组)一致的字符串进行排重,也就是将其引用同一个数组。你可以使用下面参数激活
-XX:+UseStringDeduplication
4、G1只有在发生Full GC时才进行类型卸载,但这显然不是我们想要的。你可以加上下面的参数,在并发标记阶段结束后,JVM即进行类型卸载
-XX:+ClassUnloadingWithConcurrentMark
5、建议开启选项下面的选项进行并行引用处理
-XX:+ParallelRefProcEnabled

总结

G1是一款压缩型的收集器.G1通过有效的压缩完全避免了对细微空闲内存空间的分配,不用依赖于regions,这不仅大大简化了收集器,而且还消除了潜在的内存碎片问题。除压缩以外,G1的垃圾收集停顿也比CMS容易估计,也允许用户自定义所希望的停顿参数(pause targets)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值