您的位置:  首页 > 技术 > java语言 > 正文

G1 垃圾收集器是如何对待我们 JVM

2021-09-28 11:00 https://my.oschina.net/itbbfx/blog/5270474 ITBB分享 次阅读 条评论

什么是 G1 收集器

G1 收集器是一款面向服务器端应用的垃圾收集器,它既可以用在新生代,也可以用在老年代。G1 是 Hotspot JDK1.7 后提供的面向大内存(Heap 区数 G 到数 10G )、多核系统的收集器,能够实现软停顿目标收集并且具有高吞吐量,具有更可预测的停顿时间。

G1 是一种并发、并行、部分 Stop The World、使用复制算法收集的分代的增量式收集器,G1 的全堆的操作,像global marking,是和应用(mutator)并发执行的,这样可以减少对 mutator 的暂停时间。清除阶段则使用多线程来提高吞吐量。

与 Hotspot 之前的 Serial、Parallel、CMS 等收集器不同的是,G1将堆分为很多大小相等的Region, 每次收集时会判断各Region 的活性,即垃圾对象的占比,垃圾对象占比越多的 Region,回收的收益越大,然后G1会按照设置的停顿时间目标、前几次回收 Region 所用时间来估算要回收哪些Region,即用最小的时间获取最大的收益,这也是 Garbage First 名字的含义。

Garbage First Collector 的使命是在未来替换CMS,并且在JDK1.9已经成为默认的收集器。

G1 堆内存的布局

首先是堆内存布局的变化。我们来看一下。记得堆内存布局大概是这样子的,和前面讲解的所有垃圾收集器都不一样。G1 把整个 Java 堆划分成了若干个大小相等的区域,每一个区域叫做一个 Region。Region 的大小可以通过这个参数:-XX:G1HeapgRegionSize 去指定,它的取值范围是 1 ~ 32 M,并且一定要是 2 的 n 次幂。也就是说每一个格子它的取值范围是 1 ~ 32 M。

image.png

G1 对每一个 Region 还做了分类,一共分成了四类,分别是 Eden Region、Survivor Region、Old Region 以及 Humongous Region。其中,Eden Region、Survivor Region 以及 Old Region 比较好理解,依然是分代的概念分别对应着 Eden、存活区以及老年代,只不过在 G1 里面同一个代里面的对象可能是不连续的。比如 Eden 里面的对象被划分到了多个 Eden Region 里面,就如同上图中 Eden Region 就是不连续的。

那么 Humongous Region 是要来干嘛的呢?它是用来存储大对象的,某一个对象,它的大小超过了 Region 的一半,就认为是大对象,然后存放到 Humongous Region 里面去。如果某一个对象超级大,一个 Region 甚至都存放不下,那么会分配在多个连续的 Humongous Region 里面。举个例子,假设我们使用这个参数:-XX:G1HeapRegionSize 指定 Region 的大小是 16 M,某一个对象只要超过 8 M,就会分配到某一个 Region 里面去,然后 G1 会把这个 Region 标记为 Humongous Region。某一个对象超级大,比如有 30 M,一个 Region 存不下,于是就会分配两个连续的 Region 存储这一个大对象,并且会把这两个 Region 都标记为 Humongous Region。事实上,G1 把 Humongous Region 也作为老年代的一部分看待。

G1 设计思想

了解 G1 的堆内存布局之后,我们来探讨一下 G1 的设计思想。它大致上是这样玩的,它说,我把整个 Java 堆划分成若干多个大小的 Region。然后我去跟踪每一个 Region 里面垃圾堆积的价值大小。比如我回收掉这个 Region 能够获得多少的剩余空间。再之后,G1 它在后台构建一个优先列表,你可以认为它去根据这里的价值大小做了一个排序。同时它会根据你允许的收集时间去优先回收那些价值高的 Region。这样就可以获得一个更高的垃圾回收效率。

这里可以提一下 G1 的全称叫 Garbage First,对吧?它指的意思其实是这个垃圾收集器会优先处理那些垃圾多的 Region 的意思。那么有一些文章呢,它把它翻译成叫什么,垃圾回收优先受机器。我个人觉得是不太贴切的。

好,经过分析不难发现,G1 的思想是化整为零,分而治之的。它会根据你设置的允许收集实现的阈值。一次只会去收集其中一部分 Region 的垃圾。其实本质上就是用到了前面我们学习垃圾回收算法里面的增量算法的思想。

G1 垃圾收集机制

了解 G1 的设计思想之后,我们来探讨 G1 的垃圾收集机制。

G1 提供了三种模式去收集垃圾,分别是 Young GC、Mixed GC 以及 Full GC。这三种 GC 的特性不一样,触发的条件也不一样。我们先来看一下 Young GC 是怎么玩的。

Young GC

当所有的 Edu Region 都满了的时候,就会触发 Young GC。那么这个时候,所有的 Eden Region 里面的对象都会被转移到 Survivor Region 里面去。而原先 Survivor Region 里面的对象会转移到其他的 Survivor Region 里面去。当然了,如果这个对象年龄达到了阈值,直接晋升到 Old region。最后空闲的 Region 会被放到空闲列表里面去,等待下一次被使用。

可以发现 G1 的 Young GC 和前面的年轻代回收,从过程上来说基本上是一样的。只不过现在回收的单位是 Region,对吧?我们再来探讨一下 Mixed GC 是怎么玩的。

Mixed GC

Mixed GC 是 G1 最能够体现设计思想的地方,也是它的巧妙之处。它的过程大致如下:

当老年代使用的大小已经达到整个堆的一定阈值的时候,就会触发 Mixed GC ,一旦触发 Mixed GC,会回收所有的 Young Region,同时也会回收一部分的 Old Region,注意,只是回收一部分的 Old Region,而不是所有的 Old Region,而要回收的这部分 Region 是根据前面你设置的允许回收的时间以及前面说的垃圾回收的价值去选择的。Mixed GC 回收过程大致下图所示:

image.png   第一步,初始标记。初始标记和 CMS 的初始标记是一样的,它也只是标记根对象能够直接关联到的对象。那么这一步也是需要 Stop The World ,但是时间也必要的短。

  第二步,初始标记完成之后会进入并发标记,并发标记和 CMS 的并发标记也是类似的。这个过程耗时稍微长一些。但是它可以和用户线程并发执行,所以没有 Stop The World。

第三步, 之后又会进入最终标记,最终标记也是修正在并发标记期间引起的变动。这个过程也需要 Stop The World。

第四步,最终标记完成之后就会进入筛选回收阶段。在筛选回收阶段首先会对各个 Region 的回收价值以及成本进行排序。然后去根据用户所期望的停顿时间制定一个回收计划,选择一些Region 去进行回收。这个停顿时间可以使用这个参数:-XX:MaxGCPauseMillis 去指定。

回收的过程大致如下:

首先会选择一系列的 Region 构造成一个回收集。你可以这样理解,构造了一个 set,然后把需要回收的 Region 放到这个 set 里面去,接着把这些 Region 里面的存活的对象复制到空的 Region 里面去,再把这些点的空间给清理掉。也就是说 G1 使用的是复制算法,所以 G1 是不存在内存碎片的。那么这个筛选回收阶段也是存在 Stop The World 。

所以经过分析,发现 G1 的 Mixed GC 除并发标记以外,其他过程都是 Stop The World 的。但是由于它一次只会回收一部分的 Region,所以 Stop The World 的时间是可控的。

好,我们再来探讨一下 Full GC 是怎么玩的。

Full GC

当 G1 复制对象的时候,内存不够,或者根本没有办法分配内存的时候,就会触发 Full GC,一旦触发 Full GC ,那么使用的是单线程的 Serial Old 的模式。所以一旦触发 Full GC 就会长时间的 Stop The World。

因此 G1 优化的一个重要原则就是这样尽可能的减少 Full GC 的发生,让它尽量的停留在 Young GC 或者是 Mixed GC。

那么怎么样防止 Full GC 呢?主要有三种思路:

第一,增加预留的内存,可以使用这个参数:-XX:G1ReservePercent 设置,默认阈值是堆的 10 %。

第二,让 G1 更早的回收垃圾。可以使用这个参数:-XX:InitiationHeapOccupancyPercent 去指定,默认的阈值是 45%,你可以减少这个配置的值,让它尽早的触发 Mixed GC。

第三,可以增加并发阶段所使用的线程数,这样就会有更多的垃圾回收线程区工作。当然了,这个参数会对应用的吞吐量有影响。

好,下面来总结一下 G1 这款收集器的特点:

首先 G1 它可以作用在整个堆,而其他的收集器要么作用在新生代,要么作用在老年代。

第二,G1 这款收集器它的停顿时间是可控的,你可以使用 这个参数:-XX:MaxGCPauseMillis 去指定停顿时间。

第三,G1 没有内存碎片的问题。

那么基因有哪些使用场景的?

首先它比较适合占用内存比较大的应用,一般来说呢 6G 以上就可以考虑使用 G1 这款收集器了。

第二,G1 适合去替换 MCS 垃圾收集器,这也是官方去设计 G1 的初衷。从这个角度来说,如果你的应用适合用 CMS 收集器,那么你也可以考虑使用 G1 收集器。

总结

探讨完 G1 收集器的适用场景之后,我们来对 G1 收集器做一个简单的总结。

首先 G1 它把内存划分成了若干多个区域,化整为零去回收,回收的单位不再是年轻代、老年代,而是 Region。

第二, G1 包含了 Young GC 回收新生代、Mixed GC 回收新生代以及老年代的一部分、还有Full GC 用 Serial Old 去回收垃圾。那么最能体现基于思想的是 Mixed GC。

从 Mixed GC 的执行步骤来看,G1 和 CMS 是有类似之处的。比如都有初始标记,重新标记等等。

但是也有很多的差异,主要在于 CMS 使用的是标记-清除算法,而 G1 使用的主要是复制算法,它都是把对象从一个 Region 复制到另外一个 Region,再把这个 Region 给删除掉。

那么 CMS 由于使用的是标记-清除算法,所以有内存碎片的问题。而 G1没有内存碎片的问题。

最后,在实际项目中,如果你的项目用 G1 或者是 CMS 都可以满足需求。比如我们有一个Web 应用,然后感觉用 G1 和 CMS 都可以实现目标,因为这两款垃圾收集器都可以实现低停顿的需求。

那么我们应该怎么去选择呢?可以分两种场景,如果你的项目使用的是 JDK 8,那么对于大多数服务器端应用,这两款垃圾收集器都可以去使用,并且一般来说性能的差异并不大。

如果你机器的内存不超过 6G 那么可以使用 CMS,如果内存大于 6G 可以考虑使用 G1。这主要是业界有一些测试低内存的机器,只要 CMS 性能会好一些,大内存的机器使用 G1 性能会好一些。这个临界点呢一般在 6 ~ 8G 之间。如果你的项目使用的 JDK 版本高于 JDK 8,那么请使用 G1,因为 CMS 这款收集器从 JDK 9 开始已经被废弃。

  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接