Shenandoah 与 ZGC

简介

  • Shenandoah GC 与 ZGC 同为新一代的低延迟收集器, 分别由RedHat和Oracle开发, 目前还在实验阶段, 尚未使用于生产环境。

  • GC的三项指标: Footprint(内存占用), Throughput(吞吐量) 与 Latency(延迟), 有点像CAP理论, 三者只能取其二。

  • 目前主流GC是G1, 而此两者的延时比G1低很多。

Shenandoah GC

  • 操作系统支持:

    • Linux, Windows, macOS, Solaris
  • 硬件支持:

    • x86_64

    • x86_32

    • AArch64

  • JDK支持(注意是OpenJDK的非Oracle Build, 因为Oracle不想为RedHat写的代码负责任)

    • JDK8, JDK11, JDK13
  • Shenandoah 与 G1

    • Shenandoah 可以说是G1的下一代继承者

      • 它们拥有相似的堆内存布局
      • 在初始标记, 并发标记等多阶段的处理思路上都高度一致, 甚至共享了一部分实现代码
    • Shenandoah 相比 G1的改进:

      • 堆内存管理方面:

        • 支持并发的整理算法, 能与用户线程并发

        • 默认不使用分代收集

        • 摒弃了在G1中耗费大量内存和计算资源去维护的记忆集(Memory Set), 改用名为连接矩阵(Connection Matrix) 的全局数据结构来记录跨Region的引用关系。从而降低了处理跨带指针时的记忆集维护消耗以及伪共享问题的发生概率。

          • 连接矩阵示意图(其实这张图好像错了, 第一行的X应该在[3, 1]的位置)

            “shenandoah connection matrix”的图片搜索结果

            • 说白了这就是个临接矩阵, 横坐标代表当前区域, 纵坐标代表当前区域引用的区域
            • 那么就是区域5引用了区域3, 而区域3又引用了区域1
  • Shenandoah GC的工作过程大致可划分为9个阶段

    • Initial Marking(初始标记)

      • 与G1一致, 首先标记与GC Roots直接关联的对象, 此阶段仍会"Stop The World", 但停顿时间与堆大小无关, 只与GC Roots的数量相关
    • Concurrent Marking(并发标记)

      • 与G1一致, 遍历对象图, 标记出全部可达的对象, 此阶段是与用户线程一起并发的, 时间长短取决于堆中存活对象的数量以及图的结构复杂程度
    • Final Marking(最终标记)

      • 与G1一致, 处理剩余的SATB扫描, 并在此阶段统计出回收价值最高的Region, 将这些Region构成一组回收集(Collection Set)。

      • 最终标记阶段也会有一小段短暂的停顿。

    • Concurrent Cleanup(并发清理)

      • 此阶段用于清理那些整个区域内连一个存活对象都没有找到的Region(Immediate Garbage Region)
    • Concurrent Evacuation(并发回收)

      • 在此阶段, Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region中。

      • 重点是复制对象过程不冻结用户线程而是与用户线程并行, 这一实现有很大的技术屏障, Shenandoah 通过读屏障和 Brooks Pointers(转发指针) 解决了此困难。

      • 并发回收阶段运行时间长短取决于回收集的大小

    • Initial Update Reference(初始引用更新)

      • 并发回首阶段复制对象结束后, 还需要把堆中所有指向旧对象的引用修正到复制后的新地址, 此操作称为引用更新。

      • 实际上此阶段并没有做什么实际的处理, 只是为了建立一个线程集合点, 确保所有并发回收阶段中进行的收集器线程都已完成分配给它们的对象移动任务而已。

      • 初始引用更新时间很短, 会产生一个非常短暂的停顿。

    • Concurrent Update Reference(并发引用更新)

      • 真正开始引用更新操作, 此阶段是与用户线程并发的, 时间长短取决于内存中涉及的引用数量的多少

      • 与并发标记不同, 不需要再沿着对象图来搜索, 只需要按照内存物理地址的顺序, 线性地搜索出引用类型, 把旧值改为新值即可。

    • Final Update Reference(最终引用更新)

      • 处理了堆中的引用更新后, 还需要修正存于GC Roots中的引用。

      • 此阶段是Shenandoah的最后一次停顿, 停顿事件只与GC Roots数量相关。

    • Concurrent Cleanup(并发清理)

      • 经过并发回收和引用更新之后, 整个回收集中所有的Region已再无存活对象, 这些Region都变成了Immediate Garbage Regions了。

      • 最后再调用一次并发清理过程来回收这些Region的内存空间, 供以后新对象分配使用。

  • 工作过程示意图

    img

    • 白色: 自由空间

    • 蓝色: 新分配的空间

    • 绿色: 存活的对象

    • 黄绿白: 被选入回收集的存活对象

    • 黄黄白: 需要更新引用的被选入回收集的存活对象

ZGC

  • 可以说是Oracle抄了 Azul System的作业, ZGC 与 Azul System公司的PGC(Pauseless GC) 和 C4(Concurrent Continuously Compacting Collector) 在算法和实现原理上是高度相似的, 只存在术语称谓的区别。

  • 主要特征:

    • 基于Region内存布局
    • 不设分代
    • 使用了读屏障, 染色指针和内存多重映射等技术来实现可并发的标记-整理算法
    • 以低延迟为首要目标
  • ZGC也采用基于Region的堆内存布局, 但与它们不同的是, ZGC的Region具有动态性: 动态创建和销毁, 以及动态的区域容量大小。在x64硬件平台下, ZGC的Region可以有以下三类容量:

    • Small Region

      • 容量固定为2MB, 用于放置小于256KB的小对象。
    • Medium Region

      • 容量固定为32MB, 用于放置大于等于256KB但小于4MB的对象。
    • Large Region

      • 容量不固定, 可以动态变化, 但必须为2MB的整数倍, 用于放置4MB或以上的大对象。
      • 每个大型Region中只会存放一个大对象, 它虽名为Large Region, 但它的实际容量完全有可能小于中型Region。
      • 大型Region在ZGC的实现中是不会被重分配的, 因为复制一个大对象的代价非常高昂。
  • 染色指针技术

    • 在64位系统中, 理论可以访问的内存高达16EB。实际上基于需求, 性能, 和成本考虑, 在AMD64架构中只支持到52位(4PB)的地址总线48位(256TB)的虚拟地址空间, 目前64位的硬件实际能够支持的最大内存只有256TB。此外操作系统还有自己的约束, 64Linux系统分别支持47位(128TB)的进程虚拟地址和46位(64TB)的物理地址空间, 64位的Windows系统只支持44位(16TB)的物理地址空间。

    • 虽然Linux下64位指针的高18位不能用来寻址, 剩余的46位指针所能支持的64TB内存在今天仍能够充分满足大型服务器需要。而ZGC则利用了剩下的46位指针的高4位提取出来用于存储四个标志信息

      “zgc colored pointer”的图片搜索结果

      • 通过这些标志位, 虚拟机可以直接从指针中看到其引用对象的状态, 是否进入重分配集, 是否通过finalize()方法才能被访问到。
      • 由于进一步压缩了原本只有46位的地址空间, ZGC能够管理的内存不可以超过4TB。
    • 三大优势

      • 染色指针可以使得一旦某个Region的存活对象被移走之后, 此Region立即就能够被释放和重用掉, 而不必等待整个堆中所有指向该Region的引用都被修正后才能清理。
      • 染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量。
      • 染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据, 以便日后进一步提高性能。
    • 在Linux/X86-64平台上的ZGC使用了多重映射(Multi-Mapping) 将多个不同的虚拟内存映射到同一个物理内存地址上。

      “zgc å¤šé‡æ˜ å°„â€çš„å›¾ç‰‡æœç´¢ç»“æžœ

      • 任何的进程在进程自己看来自己的内存空间都是连续的, 但是计算机实际的物理内存并不是与该进程的内存是一一对应的。碎片化的物理内存可以映射成一个完整的虚拟内存, 同时应用可以申请比物理内存大的内存, 使得多个内存互不干扰, 使编译好的二进制文件的地址统一化......
    • ZGC运作过程

      • Concurrent Mark(并发标记)

        • 遍历对象图做可达性分析的阶段, 前后也要经过类似于G1, Shenandoah 的初始标记, 最终标记的短暂停顿。

        • 与G1, Shenandoah不同的是, ZGC的标记是在指针上而不是在对象上进行的, 标记阶段会更新染色指针中的Marked0、Marked1标志位。

      • Concurrent Prepare for Relocate(并发预备重分配)

        • 此阶段需要根据特定的查询条件统计出本次收集过程要清理哪些Region, 将这些Region组成重分配集(Relocation Set)。
      • Concurrent Relocate(并发重分配)

        • 是ZGC执行过程中的核心阶段, 此过程要把重分配集中的存活对象复制到新的Region上, 并为重分配集中的每个Region维护一个转发表(Forward Table), 记录从旧对象到新对象的转向关系。

        • 由于染色指针的存在, ZGC能仅从引用上就明确得知一个对象是否处于重分配集之中。如果用户线程此时并发访问了位于重分配集中的对象, 这次访问将会被预置的内存屏障截获, 然后立即根据Region上的转发表记录将访问转发到新复制的对象上, 并同时修正该引用的值, 使其直接指向新对象, 此即为Self-Healing(自愈)[只有第一次访问旧对象会陷入转发]。

      • Concurrent Remap(并发重映射)

        • 修正整个堆中指向重分配集中旧对象的所有引用。
        • 重映射清理这些旧引用的主要目的是为了不变慢, 并不是很迫切。
        • ZGC将并发重映射阶段要做的工作, 合并到了下一次垃圾收集循环中的并发标记阶段里去完成, 从而节省了一次遍历对象图的开销。
posted @ 2020-02-23 15:23  wellDoneGaben  阅读(3254)  评论(0编辑  收藏  举报