JVM Garbage First(G1) 垃圾收集器

同优秀的CMS垃圾回收器同样,算法

  • G1也是关注最小时延的垃圾回收器,
  • 也一样适合大尺寸堆内存的垃圾收集,
  • 官方也推荐使用G1来代替选择CMS。

G1最大的特色是引入分区的思路,安全

  • 弱化了分代的概念,
  • 合理利用垃圾收集各个周期的资源,
  • 解决了其余收集器甚至CMS的众多缺陷。

串行收集器

  • 串行收集器组合 Serial + Serial Old
  • 开启选项:-XX:+SerialGC
  • 串行收集器是最基本、发展时间最长、久经考验的垃圾收集器,
    • 也是client模式下的默认收集器配置
  • 串行收集器采用单线程stop-the-world(STW)的方式进行收集。
    • 当内存不足时,
      • 串行GC设置停顿标识,待全部线程都进入安全点(Safepoint)时,
        • 应用线程暂停,串行GC开始工做,
        • 采用单线程方式回收空间并整理内存。
      • 单线程也意味着
        • 复杂度更低、
        • 占用内存更少,
        • 但同时也意味着不能有效利用多核优点。
      • 事实上,串行收集器特别适合堆内存不高、单核甚至双核CPU的场合。

并行收集器

  • 并行收集器组合 Parallel Scavenge + Parallel Old
  • 开启选项:-XX:+UseParallelGC-XX:+UseParallelOldGC(可互相激活)
  • 并行收集器是以关注吞吐量为目标的垃圾收集器
    • 也是server模式下的默认收集器配置,
    • 对吞吐量的关注主要体如今年轻代Parallel Scavenge收集器上。

并发标记清除收集器

  • 并发标记清除收集器组合 ParNew + CMS + Serial Old
  • 开启选项:-XX:+UseConcMarkSweepGC
  • 并发标记清除(CMS)是以关注延迟为目标
    • 十分优秀的垃圾回收算法,
    • 开启后,年轻代使用STW式的并行收集,
    • 老年代回收采用CMS进行垃圾回收,
    • 对延迟的关注也主要体如今老年代CMS上。
  • 年轻代ParNew与并行收集器相似,
  • 而老年代CMS每一个收集周期都要经历:
    • 初始标记、
      • 以STW的方式标记全部的根对象
    • 并发标记、
      • 并发标记则同应用线程一块儿并行,标记出根对象的可达路径
    • 从新标记、
      • 标记那些应用线程修改而引发的可能错过的可达对象
    • 并发清除
      • 最后获得的不可达对象将在并发清除阶段进行回收
    • 值得注意的是,初始标记和从新标记都已优化为多线程执行
  • CMS很是适合堆内存大、
    • CPU核数多的服务器端应用,
    • 也是G1出现以前大型应用的首选收集器
  • 可是CMS并不完美,它有如下缺点:
    • (1)因为并发进行,
      • CMS在收集与应用线程会同时会增长对堆内存的占用,
        • 也就是说,CMS必需要在老年代堆内存用尽以前完成垃圾回收,
        • 不然CMS回收失败时,将触发担保机制,
          • 串行老年代收集器将会以STW(stop-the-world)的方式进行一次GC,从而形成较大停顿时间;
    • (2)标记清除算法没法整理空间碎片,
      • 老年代空间会随着应用时长被逐步耗尽,
        • 最后将不得不经过担保机制对堆内存进行压缩。
      • CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集以后,进行一次压缩的Full GC。

Garbage First

  • Garbage First (G1)
    • 开启选项:-XX:+UseG1GC
  • 以前介绍的几组垃圾收集器组合,都有几个共同点:
    • 年轻代、老年代是独立且连续的内存块;
    • 年轻代收集使用单eden、双survivor进行复制算法;
    • 老年代收集必须扫描整个老年代区域;
    • 都是以尽量少而快地执行GC为设计原则。
  • G1垃圾收集器也是
    • 以关注延迟为目标、
    • 服务器端应用的垃圾收集器,
    • 被HotSpot团队寄予取代CMS的使命
  • G1也有相似CMS的收集动做:
    • 初始标记、并发标记、从新标记、清除、转移回收
    • 而且也以一个串行收集器作担保机制
  • G1收集与以上三组收集器有很大不一样:
    • G1的设计原则是"首先收集尽量多的垃圾(Garbage First)"。
      • G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,
        • 而是在内部采用了启发式算法,
        • 在老年代找出具备高收集收益的分区进行收集。
      • 同时G1能够根据用户设置的暂停时间目标自动调全年轻代和总堆大小,
        • 暂停目标越短年轻代空间越小、总空间就越大;
    • G1采用内存分区(Region)的思路,
      • 将内存划分为一个个相等大小的内存分区,
        • 回收时则以分区为单位进行回收,
        • 存活的对象复制到另外一个空闲分区中。
      • 因为都是以相等大小的分区为单位进行操做,
        • 所以G1自然就是一种压缩方案(局部压缩)
    • G1虽然也是分代收集器
      • 但整个内存分区不存在物理上的年轻代与老年代的区别,
      • 也不须要彻底独立的survivor(to space)堆作复制准备。
      • G1只有逻辑上的分代概念,
      • 或者说每一个分区均可能随G1的运行在不一样代之间先后切换;
    • G1的收集都是STW(stop-the-world)的,
      • 但年轻代和老年代的收集界限比较模糊,
      • 采用了混合(mixed)收集的方式。
        • 即每次收集既可能只收集年轻代分区(年轻代收集),
        • 也可能在收集年轻代的同时,包含部分老年代分区(混合收集),
        • 这样即便堆内存很大时,也能够限制收集范围,从而下降停顿。

G1的内存模型

  • 分区概念

  • 分区

    • G1采用了分区(Region)的思路,
      • 将整个堆空间分红若干个大小相等的内存区域,
      • 每次分配对象空间将逐段地使用内存。
    • 所以,在堆的使用上,
      • G1并不要求对象的存储必定是物理上连续的,
      • 只要逻辑上连续便可;
    • 每一个分区也不会肯定地为某个代服务
      • 能够按需在年轻代和老年代之间切换。
      • 启动时能够经过参数-XX:G1HeapRegionSize=n
      • 可指定分区大小(1MB~32MB,且必须是2的幂),
      • 默认将整堆划分为2048个分区
  • 卡片

    • 在每一个分区内部又被分红了若干个大小为512 Byte卡片(Card),
      • 堆内存最小可用粒度
    • 全部分区的卡片将会记录在全局卡片表(Global Card Table)中,
      • 分配的对象会占用物理上连续的若干个卡片,
      • 当查找对分区内对象的引用时即可经过记录卡片来查找该引用对象(见RSet)。
      • 每次对内存的回收,都是对指定分区的卡片进行处理。
    • G1一样能够经过-Xms/-Xmx来指定堆空间大小。
    • 当发生年轻代收集或混合收集时,
      • 经过计算GC与应用的耗费时间比,
      • 自动调整堆空间大小
    • 若是GC频率过高,则经过增长堆尺寸,来减小GC频率,
      • 相应地GC占用的时间也随之下降;
    • 目标参数-XX:GCTimeRatio即为GC与应用的耗费时间比,
      • G1默认为9,而CMS默认为99,
      • 由于CMS的设计原则是耗费在GC上的时间尽量的少。
    • 另外,当空间不足,如对象空间分配或转移失败时,
      • G1会首先尝试增长堆空间
      • 若是扩容失败,则发起担保的Full GC
      • Full GC后,堆尺寸计算结果也会调整堆空间。
  • 分代模型

  • 分代

    • G1将内存在逻辑上划分为年轻代和老年代,
      • 其中年轻代又划分为Eden空间和Survivor空间。
    • 但年轻代空间并非固定不变的,
      • 当现有年轻代分区占满时,JVM会分配新的空闲分区加入到年轻代空间。
    • 整个年轻代内存会在初始空间-XX:G1NewSizePercent(默认整堆5%)与最大空间-XX:G1MaxNewSizePercent(默认60%)之间动态变化,
      • 且由参数目标暂停时间-XX:MaxGCPauseMillis(默认200ms)、须要扩缩容的大小以及分区的已记忆集合(RSet)计算获得。
      • 固然,G1依然能够设置固定的年轻代大小(参数-XX:NewRatio、-Xmn),但同时暂停目标将失去意义。
  • 本地分配缓冲

    • 本地分配缓冲 Local allocation buffer (Lab)
    • 因为分区的思想,
      • 每一个线程都可以"认领"某个分区用于线程本地的内存分配,
      • 而不须要顾及分区是否连续。
    • 所以,每一个应用线程和GC线程都会独立的使用分区,
      • 进而减小同步时间,提高GC效率,这个分区称为本地分配缓冲区(Lab)
    • 其中,应用线程能够独占一个本地缓冲区(TLAB)来建立的对象,
    • 而大部分都会落入Eden区域(巨型对象或分配失败除外),
      • 所以TLAB的分区属于Eden空间;
    • 而每次垃圾收集时,每一个GC线程一样能够独占一个本地缓冲区(GCLAB)用来转移对象,
      • 每次回收会将对象复制到Suvivor空间或老年代空间;
    • 对于从Eden/Survivor空间晋升(Promotion)到Survivor/老年代空间的对象,
      • 一样有GC独占的本地缓冲区进行操做,
      • 该部分称为晋升本地缓冲区(PLAB)。
  • 分区模型

    • G1对内存的使用以分区(Region)为单位,而对对象的分配以卡片(Card)为单位
  • 巨型对象

    • 巨型对象 Humongous Region
      • 一个大小达到甚至超过度区大小一半的对象称为巨型对象(Humongous Object)。
    • 由于巨型对象的移动成本很高,
      • 并且有可能一个分区不能容纳巨型对象。
    • 所以,巨型对象会直接在老年代分配,
      • 所占用的连续空间称为巨型分区(Humongous Region)。
    • 巨型对象会独占一个、或多个连续分区,
      • 其中第一个分区被标记为开始巨型(StartsHumongous),
      • 相邻连续分区被标记为连续巨型(ContinuesHumongous)。
  • 已记忆集合

    • 已记忆集合 Remember Set (RSet)
    • 在串行和并行收集器中,
      • GC经过整堆扫描,来肯定对象是否处于可达路径中
    • G1为了不STW式的整堆扫描,
      • 在每一个分区记录了一个已记忆集合(RSet),
      • 内部相似一个反向指针,
      • 记录引用分区内对象的卡片索引。
    • 当要回收该分区时,经过扫描分区的RSet,
      • 来肯定引用本分区内的对象是否存活,
      • 进而肯定本分区内的对象存活状况。
    • 事实上,并不是全部的引用都须要记录在RSet中,
      • 若是一个分区肯定须要扫描,那么无需RSet也能够无遗漏的获得引用关系。
        • 那么引用源自本分区的对象,固然不用落入RSet中;
        • 同时,G1 GC每次都会对年轻代进行总体收集,所以引用源自年轻代的对象,也不须要在RSet中记录。
        • 最后只有老年代的分区可能会有RSet记录,
          • 这些分区称为拥有RSet分区(an RSet’s owning region)。
  • Per Region Table

    • RSet在内部使用Per Region Table(PRT)记录分区的引用状况
    • 因为RSet的记录要占用分区的空间,
      • 若是一个分区很是"受欢迎",
      • 那么RSet占用的空间会上升,从而下降分区的可用空间。
      • G1应对这个问题采用了改变RSet的密度的方式
        • 在PRT中将会以三种模式记录引用:
          • 稀少:直接记录引用对象的卡片索引
          • 细粒度:记录引用对象的分区索引
          • 粗粒度:只记录引用状况,每一个分区对应一个比特位
        • 粗粒度的PRT只是记录了引用数量
          • 须要经过整堆扫描才能找出全部引用,
          • 所以扫描速度也是最慢的。
  • 收集集合 (CSet)

    • 收集集合(CSet)表明每次GC暂停时回收的一系列目标分区。
    • 年轻代收集集合

      • 年轻代收集集合 CSet of Young Collection
      • 当JVM分配对象到Eden区域失败(Eden区已满)时,
        • 便会触发一次STW式的年轻代收集。
      • 在年轻代收集中,
        • Eden分区存活的对象将被拷贝到Survivor分区;
        • 原有Survivor分区存活的对象,
          • 将根据任期阈值(tenuring threshold)分别晋升到PLAB中,
          • 新的survivor分区和老年代分区。
        • 而原有的年轻代分区将被总体回收掉。
      • 同时,年轻代收集还负责维护对象的年龄(存活次数),
        • 辅助判断老化(tenuring)对象晋升的时候是到Survivor分区仍是到老年代分区。
        • 年轻代收集首先先将晋升对象尺寸总和、对象年龄信息维护到年龄表中,再根据年龄表、Survivor尺寸、Survivor填充容量-XX:TargetSurvivorRatio(默认50%)、最大任期阈值-XX:MaxTenuringThreshold(默认15),
        • 计算出一个恰当的任期阈值,凡是超过任期阈值的对象都会被晋升到老年代。
    • 混合收集集合

      • 混合收集集合 CSet of Mixed Collection
      • 当老年代占用空间超过整堆比IHOP阈值-XX:InitiatingHeapOccupancyPercent(默认45%)时,
        • G1就会启动一次混合垃圾收集周期
      • 为了知足暂停目标(暂停的时间限制),
        • G1可能不能一口气将全部的候选分区收集掉,
        • 所以G1可能会产生连续屡次的混合收集与应用线程交替执行,
        • 每次STW的混合收集与年轻代收集过程相相似。
相关文章
相关标签/搜索