zgc | 您所在的位置:网站首页 › zgc简介 › zgc |
一、背景&简介
Java11 推出的最新垃圾收集器,ZGC,主要为了减少JVM停顿时间。 ZGC 收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。 ZGC 全称是Z Garbage Collector,是一款可伸缩的、低延迟、并发垃圾回收器,旨在实现以下几个目标: 停顿时间不超过10ms 停顿时间不随heap大小或存活对象大小增大而增大 可以处理从几百兆到几T的内存大小(最大4T)如何减少JVM停顿时间? GC可以在压缩时使用多个线程。(并行压缩) 压缩工作也可以分解为多个阶段。(增量压缩) 将堆压缩,却不停止正在运行的应用程序(或只是短时间停止)。(并发压缩) 完全不压缩。如何进行标记? 把标记直接记录在对象头上(如Serial收集器) 把标记记录在与对象相互独立的数据结构上(如G1、Shenandoah使用了一种相当于堆内存的1/64大小的,称为BitMap的结构来记录标记信息) 直接把标记信息记在引用对象的指针上(如ZGC) 二、ZGC的堆内存布局分类如下: 小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。 中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象。 大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,所以实际容量可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的实现中是不会被重分配的,因为复制一个大对象的代价非常高昂。 三、名词/技术点详解 虚拟内存映射技术(Memory-Mapped I/O, MMIO)和分页机制(一级页表)(以32位为例)目的:通过给的虚拟地址(一个32位数字) 来求出 物理地址(一个32位数字)
相对物理块来说,页是逻辑地址空间(虚拟内存空间)的划分,是逻辑地址空间顺序等分而成的一段逻辑空间,并依次连续编号。页的大小一般为 512B~8KB。 例如:一个 32 位的操作系统,页的大小设为 2^12=4Kb,那么就有页号从 0 编到 2^20 的那么多页逻辑空间。 2. 物理块物理块则是相对于虚拟内存对物理内存按顺序等大小的划分。物理块的大小需要与页的大小一致。 例如:2^32=4Gb 的物理内存,按照 4Kb/页的大小划分,可以划分成物理块号从 0 到 2^20 的那么多块的物理内存空间。 3. 页表页表是记录逻辑空间(虚拟内存)中每一页在内存中对应的物理块号。 页表是一个数组,数组长度和逻辑空间数量一致(2^20),值是一个32位数值,其中高20位为物理块号,低12位存储该物理块属性信息(例如是否有读写权限、是否已经分配物理内存、是否被换出到硬盘等) 4. 逻辑地址结构(虚拟内存地址/指针)逻辑地址是一个32位数值,高20位存储页表的索引(index),低12位存储在对应物理块的偏移量(offset)。
从逻辑地址高20位(index)访问页表取得页表存储的值(value),从页表存储的值(value)的高20位拿到物理块地址,然后用逻辑地址的低12位(offset)对该物理块进行偏移就可以得到。 因为需要将4GB逻辑地址中一部分要划分出来与BIOS ROM、CPU寄存器、I/O设备这些部件的物理地址进行映射 染色指针是一种直接将少量额外的信息存储在指针上的技术。 目前在Linux下64位的操作系统中高18位是不能用来寻址的,但是剩余的46为却可以支持64T的空间,到目前为止我们几乎还用不到这么多内存。于是ZGC将46位中的高4位取出,用来存储4个标志位,剩余的42位可以支持4TB(2^42)的内存,也直接导致ZGC可以管理的内存不超过4TB。显然32位不够,故zgc无法在32位系统工作。 更详细的 ASCII 图如下 +-------------------+-+----+-----------------------------------------------+ |00000000 00000000 0|0|1111|11 11111111 11111111 11111111 11111111 11111111| +-------------------+-+----+-----------------------------------------------+ | | | | | | | * 41-0 Object Offset (42-bits, 4TB address space) | | | | | * 45-42 Metadata Bits (4-bits) 0001 = Marked0 | | 0010 = Marked1 | | 0100 = Remapped | | 1000 = Finalizable | | | * 46-46 Unused (1-bit, always zero) | * 63-47 Fixed (17-bits, always zero) 1. 状态位详解 Marked0/marked1: 判断对象是否已标记。 Remapped: 判断该对象是否在relocation set中 Finalizable: 判断对象是否只能被Finalizer方法访问 2. 为什么两个mark位?每一个GC周期开始时,会交换使用的标记位,使上次GC周期中修正的已标记状态失效,所有引用都变成未标记。 GC周期1:使用mark0, 则周期结束所有引用mark标记都会成为01。 GC周期2:使用mark1, 则mark标记10,所有引用都能被重新标记。 3. 为什么逻辑地址可以用多个地址(不同的指针颜色)指向同一个物理内存?由于存在虚拟内存映射,将不同的分段index所在页表的值改成同一个即可。
gc屏障是一个类似aop的功能,当进行对应操作时,在操作前(X前屏障)或者后(X后屏障)完成某些功能。(区别于volatile带来的内存屏障) 与标记对象的传统算法相比,ZGC在指针上做标记,在访问指针时加入Load Barrier(读屏障),比如当对象正被GC移动,指针上的颜色就会不对,这个屏障就会先把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速,而不存在为了保持应用与GC一致而粗暴整体的Stop The World。 ZGC使用read barrier,即对指向堆的引用进行读取时,会发生read barrier,比如 obj.field //加载堆中对象的引用,触发load barrierZGC不使用write barrier: obj.field = value //不使用write barrier,此时不会触发load barrier 2. 指针的自愈能力在ZGC中,当读取处于重分配集的对象时,会被读屏障拦截,通过转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象。ZGC将这种行为叫做指针的“自愈能力”。 好处是:第一次访问旧对象访问会变慢,但也只会有一次变慢,当“自愈”完成后,后续访问就不会变慢了。 Object a = obj.x; Object b = obj.x;两行代码都插入了读屏障,但ZGC在第一个读屏障之后,不但a的值是新的,self healing下obj.x的值自身也会修正,第二个读屏障时就直接进入FastPath,没有消耗了; 而Shenandoah 则不会修正obj.x的值,第二个读屏障又要SlowPath一次。 四、工作流程/原理逻辑上一次ZGC分为Mark(标记)、Relocate(迁移)、Remap(重映射)三个阶段: Mark: 所有活的对象都被记录在对应region的Livemap(活对象表,bitmap实现)中,以及对象的Reference(引用)都改成已标记(Marked0或Marked1)状态 Relocate: 根据页面中活对象占用的大小选出的一组region,将其中的活对象都复制到新的region,并在额外的forward table(转移表)中记录对象原地址和新地址对应关系 Remap: 所有Relocated的活对象的引用都重新指向了新的正确的地址 由于想要将所有引用都修正过来需要跟Mark阶段一样遍历整个对象图,所以这次的Remap会与下一次的Remark阶段合并。所以在GC的实现上是2个阶段,即Mark&Remap阶段和Relocate阶段 1. 初始标记 STW 与G1、Shenandoah一样,标记出root节点。停顿时间和堆大小无关,只和GC Roots数量有关。![]() ![]() ![]() ![]() ![]() 如果对整个堆做一个完整并发收集周期,持续的时间可能很长比如几分钟,而此期间新创建的对象,大致上只能当作活对象来处理,即使它们在这周期里其实早就死掉可以被收集了。如果有分代算法,新生对象都在一个专门的区域创建,专门针对这个区域的收集能更频繁更快,意外留活的对象更也少。 如有错误,欢迎指正! 参考资料 oracle zgc ppt Java’s new Z Garbage Collector (ZGC) is very exciting 知乎问题: ZGC 原理是什么,它为什么能做到低延时? R大回答 Java程序员的荣光,听R大论JDK11的ZGC 新的Java垃圾回收机制ZGC 简介 |
CopyRight 2018-2019 实验室设备网 版权所有 |