博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JVM Garbage First(G1) 垃圾收集器
阅读量:7039 次
发布时间:2019-06-28

本文共 5271 字,大约阅读时间需要 17 分钟。

hot3.png

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

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

G1最大的特点是引入分区的思路,

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

串行收集器

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

并行收集器

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

并发标记清除收集器

  • a0d49100047474542e36b6e0d9c9dcd03f0.jpg
  • 并发标记清除收集器组合 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

  • f88c2c2d9fb03e25ebe7a5643e25a4988c5.jpg
  • 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的内存模型

  • 分区概念

    • e2702bb1c429591c024df80310e467e7585.jpg
  • 分区

    • 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后,堆尺寸计算结果也会调整堆空间。
  • 分代模型

    • 70eac9e859b84438f097956c2bc09c3bec5.jpg
  • 分代

    • 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)。
  • 分区模型

    • 10be73f70bf878ceb27f92b35a1787e4aa3.jpg
    • 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)

    • a593f767896cd638891855f7bde185859fd.jpg
    • 收集集合(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的混合收集与年轻代收集过程相类似。

 

 

 

 

转载于:https://my.oschina.net/u/3847203/blog/3016025

你可能感兴趣的文章
C#窗体控件更新(二)
查看>>
Android的一些UI设计
查看>>
Cisco路由器配置 IPsec ***
查看>>
Linux下搭建LAMP环境
查看>>
华为交换机配置方法
查看>>
Linux下实行计划任务介绍
查看>>
LWUIT + ChartComponent实现折线表
查看>>
iptables example
查看>>
JavaScript 在页面上的位置
查看>>
52、组播Multicast之IGMP
查看>>
4.VMware View 4.6安装与部署-view composer
查看>>
mysql的备份与恢复
查看>>
mysql主从配置
查看>>
Android生命周期图文介绍
查看>>
初涉 Git 心得
查看>>
私有VLAN知识点
查看>>
Shell 脚本介绍
查看>>
Open×××的服务器端和客户端搭建(二)
查看>>
Redis 键(key)相关的命令及其它命令的查看地址
查看>>
vm虚拟机怎么访问本地硬盘
查看>>