JVM 垃圾收集器之G1概念学习

760 阅读8分钟

Garbage First Collector

G1 和以前学的垃圾收集器有些不同

  • 首先,堆的划分有些区别
    • 其它收集器将内存划分成固定的大小,即物理上的划分成新生代(Eden和两个Servivior)和老年代。
    • G1 是逻辑上的划分,在内存上没有正在划分。 image.png

一些概念

吞吐量

  • 吞吐量关注的是,在一个指定的时间内,最大化一个应用的工作量
  • 如下方式来衡量一个系统吞吐量的好坏:
    • 在一小时内同一个事务(或者任务、请求)完成的次数(tps)
    • 数据库一小时可以完成多少次查询
  • 对于关注吞吐量的系统,卡顿(STW) 是可以接受的,因为这个系统关注长时间的大量任务的执行能力,单次快速的响应并不值得考虑

响应能力

  • 响应能力指一个程序或者系统对请求是否能够及时响应,比如:
    • 一个桌面UI能多快地响应一个事件。
    • 一个网站能够多快返回一个页面请求数据库能够多快返回查询的数据
  • 对于这类对响应能力敏感的场景,长时间的停顿是无法接受的

G1 简介

适用场景

  • g1收集器是一个面向服务端的垃圾收集器适用于多核处理器、大内存容量的服务端系统
  • 它满足短时间gc停顿的同时达到一个较高的吞吐量。
    • 高响应高吞吐。
  • JDK7以上版本适用。

G1收集器的设计目标

  • 与应用线程同时工作,几乎不需要stop theworld(与CMS类似)。
  • 整理剩余空间,不产生内存碎片(CMS只能在FullGC时,用stop the world整理内存碎片)。
  • GC停顿更加可控。
    • 如果是CMS ,发生Full GC 时的STW无法估计时间。
    • G1 可以设置最大停顿(STW)时间,它会尽量的满足所设值。
    • 得益于G1 的内存的设计,Full GC 不一定要全部执行完。
  • 不牺牲系统的吞吐量。
  • gc不要求额外的内存空间(CMS需要预留空间存储浮动垃圾)。
    • 因为CMS是和用户线程并行的,可能会出现已经被识别为不是垃圾的东西,被用户线程丢了。

G1的设计规划是要替换掉CMS

  • G1在某些方面弥补了CMS的不足,比如,CMS使用的是mark-sweep算法,自然会产生内存碎片。
  • 然而G1基于copying算法,高效的整理剩余内存,而不需要管理内存碎片。
  • 另外,G1提供了更多手段,以达到对gc停顿时间的可控。
  • 但是还是要具体问题具体分析,有时候CMS也是会比G1好的。

G1收集器堆结构

  • heap被划分为一个个相等的不连续的内存区域(regions),每个region都有一个分代的角色:eden、survivor、old
  • 对每个角色的数量并没有强制的限定,也就是说对每种分代内存的大小,可以动态变化G1最大的特点就是高效的执行回收,优先去执行那些大量对象可回收的区域(region)
  • G1使用了gc停顿可预测的模型,来满足用户设定的gc停顿时间,根据用户设定的目标时间,G1会自动地选择哪些region要清除,一次清除多少个region。
  • G1从多个region中复制存活的对象,然后集中放入一个region中,同时整理、清除内存(copying收集算法)
    • 类似将Eden 区的内容移到Servivor,差不多。

G1 vs CMS

  • 对比使用mark-sweep的CMS,G1使用的copying算法不会造成内存碎片
  • 对比Parallel Scavenge(基于copying )、Parallel Old收集器(基于mark-compact-sweep),Parallel会对整个区域做整理导致gc停顿会比较长而G1只是特定地整理几个region。
  • G1并非一个实时的收集器,与parallel Scavenge一样,对gc停顿时间的设置并不绝对生效,只是G1有较高的几率保证不超过设定的gc停顿时间。
  • 与之前的gc收集器对比,G1会根据用户设定的gc停顿时间,智能评估哪几个region需要被回收可以满足用户的设定。

分区(Region)

  • G1采取了不同的策略来解决并行、串行和CMS收集器的碎片、暂停时间不可控等问题——G1将整个堆分成相同大小的分区(Region)。

关于G1 的分代

  • 每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代
  • 年轻代、幸存区、老年代这些概念还存在,成为逻辑上的概念这样方便复用之前分代框架的逻辑。
  • 在物理上不需要连续,则带来了额外的好处——有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区这样可以花费较少的时间来回收这些分区的垃圾这也就是G1名字的由来,即首先收集垃圾最多的分区(Garbage First)。
    • G1垃圾收集器将堆内存划分为多个大小相等的区域(Region),每个区域可以包含多个对象。在垃圾收集过程中,G1会优先选择那些包含垃圾对象较多的区域进行回收,以最大程度地释放内存空间。
  • 依然是在新生代满了的时候,对整个新生代进行回收——整个新生代中的对象,要么被回收、要么晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小。
  • G1还是一种带压缩的收集器在回收老年代的分区时,是将存活的对象从一个分区拷贝到另一个可用分区,这个拷贝的过程就实现了局部的压缩

Cset

  • 收集集合(CSet):一组可被回收的分区的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自eden空间、survivor空间、或者老年代。

Rset

  • 已记忆集合(RSet): RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。
    • 记录的是关系,谁引用了我的分区。
    • 需要消耗额外的空间。 image.png
  • G1 GC是在points-out的card table之上再加了一层结构来构成points-into RSet:每个region会记录下到底哪些别的region有指向自己的指针,而这些指针分别在哪些card的范围内。
  • 这个RSet其实是一个hash tablekey是别的region的起始地址value是一个集合里面的元素是card table的index
    • 举例来说,如果region A的RSet里有一项的key是region B,value里有index为1234的card,它的意思就是region B的一个card里有引用指向region A。所以对region A来说,该RSet记录的是points-into的关系;而card table仍然记录了points-out的关系。

SATB

  • Snapshot-At-The-Beginning(SATB):SATB是G1 GC在并发标记阶段使用的增量式的标记算法
  • 并发标记是并发多线程的,但并发线程在同一时刻只扫描一个分区

概念小结

  • Region
  • Cset
  • Rset
  • SATB

更权威和准确的关于G1的知识应该去看官方文档

总结 G1 比 CMS 的优势

优势小结

  • G1在压缩空间方面有优势。
  • G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
  • Eden、Survivor、Old区不再固定,在内存使用效率上来说更灵活
  • G1可以通过设置预期停顿时间(Pause Time)来控制圾收集时间,避免应用雪崩现象。
  • G1在回收内存后会马上同时做合并空闲内存的工作,而CMS默认是在STW ( stop the world)的时候做
  • G1会在Young GC中使用,而CMS只能在O区使用。
    • 年轻代也使用的复制算法

G1的适用场景小结

  • 服务端多核CPU、JVM内存占用较大的应用。
  • 应用在运行过程中会产生大量内存碎片、需要经常压缩空间。
  • 想要更可控、可预期的GC停顿周期。
  • 防止高并发下应用的雪崩现象。

G1 垃圾回收步骤