JAVA中的垃圾回收器(3)----ZGC

一)ZGC介绍:是JAVA15新引入的低延迟垃圾收集器

ZGC是一款基于分区的内存布局的垃圾收集器,使用了读屏障染色指针和并发重映射转发表等技术来实现并发的标记整理算法,以低延迟为主要目标的垃圾收集器,在G1时代要设置最大停顿时间,但是ZGC统一将最大停顿时间控制在10ms

ZGC是一款基于Regin的,暂时没有分代概念的,使用了读屏障,颜色指针等技术来实现并发的标记清除和标记整理算法,一低延迟为主要目标的一款垃圾回收器

ZGC的regin可以分为是下图中具有大中小三类容量

1)小型regin:容量固定是2MB,用于存放小于256K的小对象

2)中型regin:容量固定是32MB,用于存放大于等于256KB但是小于4MB的对象

3)大型regin:容量不固定,可以动态变化,但必须是2MB的整数倍,用来存放4MB以上的大对象,但是他的实际容量可能小于中型regin,大型regin在进行ZGC的时候是不会被重分配的,并发重分配是ZGC的一种处理动作,用于复制对象的收集器阶段,因为复制一个大对象的代价非常高昂;

uma:所有CPU都是统一访问这一块内存,如果多核CPU来争抢同一块内存,那么此时就会涉及到线程安全问题,可能涉及到内存资源分配争抢的问题,可能会有争抢或者是CAS机制影响程序性能,尽管TLAB做一些内存分配增强的优化

UMA代表内存只是存在一块,所有的CPU都去访问这一块内存,那么就会存在竞争问题来争抢内存总线访问权,有竞争就会有锁,有锁效率就会受到影响,而且CPU核数越多,竞争就越激烈;

numa:是CPU访问内存的一个架构,统一内存访问架构,将一个大堆内存分开成一小块一小块的内存来供给每一块供给某一个CPU来优先进行访问,避免了大多数分配内存和使用内存的资源争抢问题,ZGC可以自动识别NUMA架构

NUMA的话每一个CPU对应这一块内存,况且这块内存在主板上是离CPU是很近的,每一个CPU来优先访问这块内存,那么效率自然就提高了,服务器的NUMA架构在中大型系统上一直非常盛行,也是高性能的解决方案,尤其在系统延迟方面表现都很优秀,ZGC是能自动感知NUMA架构并充分利用NUMA架构特性的;

ZGC的优缺点:

1)支持TB级别的堆:生产环境的硬盘还没有上TB呢,可以满足未来十年内,JAVA应用的所有需求了;

2)最大GC停顿时间不超过10ms:垃圾回收几乎实现了全局并发执行;

3)未来GC奠定的基础

4)停顿时间不会随着堆增大而增大,也就是说几十G堆的停顿时间是10ms以下几百G甚至是上TB级别的堆停顿时间也是10ms以下;

缺点:

1)只是适用于32位系统

2)最大只是支持4TB内存容量

3)最糟糕的情况下吞吐量会下降15%,这都不是事至于吞吐量,通过扩容分分钟解决

4)分代的原因:不同对象的生命周期不相同,可能会扫描整个堆

如果堆空间越来越大,那么垃圾回收的造成的STW的时间会呈现线性的增长

堆空间分页模型:小页面优先回收,大页面尽量不回收

ZGC本身只是支持三种页面,分别是小页面,中页面,大页面,其中小页面指的是2MB的内存空间,中页面指的是32MB的内存空间,大页面指的是操作系统分配的大页

当对象大小小于等于256KB时,对象分配在小页面,当对象大小在256KB和4M之间,对象分配在中页面,当对象大于4M,对象分配在大页面,ZGC对于不同页面回收的策略也不同。简单地说,小页面优先回收;中页面和大页面则尽量不回收;

1)ZGC会自动识别NUMA架构,把一块大的内存拆分成若干个小内存来提供个不同的CPU来进行使用,每一块内存会让一个CPU优先访问;

2)ZGC小页面优先回收,大页面尽量不回收,可以选取垃圾特别多的页面进行回收,一个页面垃圾越多,那么回收的优先级就越高,如果将所有的页面都进行回收,那么回收的效率就特别低;

ZGC中的转移:如果是同一个页面等同于标记整理算法,如果是不同的页面,等同于复制算法

标记:从根集合出发,标记活跃对象,此时内存存在着活跃对象和死亡对象

转移:把活跃对象转移复制到新的内存上面,原来的内存空间可以回收

重定位:因为对象的内存地址发生了变化,所以所有的指向对象的老地址的指针都要调整到对象的新的地址上面

三色标记漏标:就是用来修复并发标记中的漏标记问题,就是将B扫描完成之后,B就会变成黑色,此时B引用着D,D引用着E,此时如果在并发标记过程中D指向E的指针没了(D还没有来得及指向E),此时B指向了E,此时因为B已经变成了黑色,B不会重新扫描,但是此时的E就变成了一个垃圾对象,此时就发生了漏标操作CMS使用的是增量更新G1使用的是原始快照

B C D此时扫描完成,变成绿色,M0标记位是1,此时假设B C D 扫描完成之后发现B对象指向了D对象,此时D被其他对象指向了,但是此时B不会被扫描了,此时就出现漏标的情况,此时D就是蓝色的对象就被认为是垃圾,并发标记完成变成绿色;

二)指针着色技术: 

虚拟机栈(栈帧中的本地变量表)+方法区中类的静态变量+方法区中的常量+本地方法栈

1)颜色指针是ZGC的核心概念,因为zgc在指针中借了几位来搞事情,所以它必须要求在64位的系统上,2^10=1K,2^20=1G,2^32=4G,64位系统可以达到4TB;

2)在ZGC中使用低42位来表示使用中的堆空间(真实内存),ZGC借助高几位来做GC相关的事情,快速实现垃圾回收中的并发标记,转移和重定位等等;

3)这几个标记位具有互斥性,这几个标志位有且只有一个位置为1,某一个标志位为1表示有颜色;

4)ZGC只是支持64位系统,64位指针,ZGC中低42位表示的是堆空间的地址范围,因为42位是4个T,ZGC借助高几位来实现做GC垃圾回收相关的事情,用于快速实现垃圾回收中的并发标记,转移和重定位等,这几个高位是存在互斥性的,这几个位置有且只能有一个位置置为1,只要这个位置是1,那么指针就会带有颜色,颜色指针表示的是某一个比特位是1了,那么就代表着这个指针带有颜色了,在进行垃圾回收的时候,和对象头就没有关系了,其他的垃圾回收器在进行垃圾标识的时候会把垃圾的状态写到对象头里面,但是在ZGC里面它使用的不是对象头而是指针的着色技术;

System.gc()会被很多垃圾回收器不一定会调用;

颜色指针的概念:CMS和以前的垃圾回收器是标记在对象内部的,但是ZGC是标记在指向对象的引用上面,内部会分配4个比特位专门去做标记,这就叫做颜色指针,下面是初始状态,GC垃圾回收器还没有启动:

1)初始情况下,A,B,C,D在堆上面的不同地址中,为了方便演示,对象地址就使用1-9来表示,况且都被GCROOTS引用给关联着

2)G1垃圾回收器没有启动的时候如果new 对象,那么指针的颜色就是蓝色,也就是Remapped他的标记位就是1,这个对象已经完成重定位,当指针内存地址指向真正的对象的内存地址,那么此时Remapped标记为就是1,就是刚刚new出来的对象或者是垃圾回收器已经转移完成的对象(已经完成重定位);

三)ZGC的垃圾回收过程:

Remapped代表蓝色表示已经完成并发转移(在垃圾回收器启动以前)并且更新完成指针的对象(并发转移移动到新区域以后被标记成蓝色),在最终标记以后开始并发转移以前蓝色的对象是没有标记过的对象垃圾对象

M0:本次垃圾回收过程中或者是上一次垃圾回收中被标记的对象,区分两次垃圾回收

M1:本次垃圾回收过程中或者是上一次垃圾回收中被标记的对象,区分两次垃圾回收

1)初始标记:只是进行标记GCroots直接关联的对象,此时A的颜色是绿色,M0为1表示颜色是绿色,代表已经标记过的对象,代表这本次垃圾回收对象的指针标记过的颜色,因为此时GCROOTS直接关联的对象是A,那么ZGC就把A指针直接标记成绿色,代表A已经被标记过

初始标记存在STW,但是时间很短,初始标记就是只是需要扫描所有的GCROOTS直接关联的对象;

2)并发标记:这个过程扫描引用链上面的所有对象,这段时间GC的处理时间非常长,但是没有STW,业务线程可以继续进行执行,但是会产生漏标问题;

3)重新标记:此时需要使用原始快照来解决漏标方案,会产生STW,比初始标记快,比并发标记慢一些,经过重新标记以后,整个堆内存中蓝色的对象就是垃圾对象,如果此时有新来的new对象就会被标记成蓝色;

在并发标记完成以后并发转移准备,JVM判断如果某一个对象还是蓝色况且是在旧区域,不是红色或者是绿色,那么说明这个对象压根就没有标记过,说明此时这个对象还是不可达;

ZGC将G1的筛选回收进行拆分,拆分成了三个步骤:

4)并发预备重分配:没有STW

在内部会使用一些算法,计算一下有哪些大概有哪些regin要进行清理和G1里面计算回收比例是一样的,那一块回收比例大一些就会回收哪一块,没有STW,并且还为对象分配新的内存地址,不会阻塞应用线程的执行;

4.1)进行分析,哪些页面有垃圾,哪些页面的垃圾有多少,占多少总页面比例,并进行分析,并针对于垃圾较多的区域优先进行回收,作为一个优先级别;

4.2)如果发现一个区域地方全是垃圾,然后可以在并发转移准备并发全部进行清理,对业务程序没有任何影响,这个阶段用户线程和垃圾回收线程一起跑;

5)初始转移:存在STW,做对象重定位

对应着初始标记的对象将GCROOTS直接关联的对象进行复制算法转移到另一个位置,初始转移的对象都是绿色的对象,将A的引用指针进行更新,并且将MO状态转换到Remapped状态,这个过程会造成STW,存在短暂的STW;

5.1)先对页面作短暂的STW,将GCROOTS直接关联的对象(地址1)A转移到小页面中的内存地址是10的地方;

5.2)跟新指向A对象的指针;

5.3)将指向A对象的指针变成蓝色(已经从初始阶段完成了并发转移),改之前是绿色的(代表完成了初始标记但是还没有完全完成并发转移),下面是初始转移完成的情况:

六)并发转移:惰性更新(引用指针的42位地址指向的都是老的地址通过转发表变成新的)

在以前的垃圾回收器中,这个过程都是STW的,但是ZGC却采用了转移对象采取和用户线程一起执行,ZGC额外的在堆内存维护了一张转发表,用来维护新旧地址的映射

1)使用复制算法将老对象移动到新的区域里面,此时对象的指针还是绿色,属于标记状态

2)使用转发表维护新旧对象的地址的映射,注意做对象的转移和插入转发表中的记录是原子操作,要么全部执行成功,要么全部执行失败;

3)本次并发转移过程中并不会更新新对象的地址到指针里面,就使用转发表来进行存储,当程序需要访问转发表来读取被移动的对象的时候,采用读屏障来做引用地址的更新

此时如果有新来的对象,放到新区域里面并标记成蓝色

STW:要进行重定位吞吐量会严重减少,STW时间会非常少

此时就没有必要把A放到转发表里面了,因为A已经转移完成了

并发转移:对应处理着并发标记标记的对象,转移MO标记有效,也就是绿色的对象,使用复制算法将新的对象移动到另一块空间里面,但是这个时候用户线程还在执行,如果是STW,那么在STW的时间范围内做地址的引用更新是比较简单的,但是此时并发重分配是合用户线程一起执行的,没有STW,复制算法挪完对象之后该如何更新指针原引用,这个过程就变得比较复杂了,ZGC不会马上去把引用进行更新因为修改老的引用会有严重的并发控制问题

a)复制算法复制对象到一个空区域,复制对象以后不会直接进行修改原引用的指针

b)ZGC底层使用的是读屏障也叫做内存屏障,什么修改新的引用指针呢?针对于引用做一个读取操作的时候才会修改这个指针里面的值,才会修改这个引用最新的对象的地址,并发转移最终不会终止业务线程和业务线程并发执行的,业务线程此时看到的对象的指针是不会进行修正的,此时就会发生问题,这个时候使用一个转发表,就是记录指针新旧地址的映射HashMap<老地址,新转移的地址>,那么此时如果在再次读取这个转移的对象,那么此时就可以根据转发表进行二次转发找到这个对象,但是对象转移和查转发表是原子操作的,此时A就不用记录在转发表,在初始标记阶段已经把A的指针的引用关系修改了,存在STW;

经历过并发转移以后,原有的老对象的内存空间就被清理掉了,但是B C对象指针还指向的是老对象(堆中已经不存在)的地址;

那么此时如果在第一次垃圾回收和第二次垃圾回收之间new出来的对象,此时存放到新的地址里面,标记成蓝色就是Remapped

6)并发重映射:

原来对象的其他老的引用指向的不是垃圾的对象A,现在这个对象A移动了位置,需要将那些引用指向新移动的A的对象位置,其实最终这一个步骤不做也可以,因为可以通过颜色屏障可以自动得将那些要修改的引用对象地址做一个更新,老的引用就不执行这个阶段也没啥事,这个过程放到下一次垃圾回收的并发标记阶段来做上次并发转移对象的重定位

在两次GC业务之间,访问那些没有完成并发重定位的对象所依据的就是读屏障 

ZGC的第二次垃圾回收:

1)第二次垃圾回收的初始标记:标记A,M1的颜色此时是红色,这时候的红色代表的是本次垃圾回收的被标记对象指针的颜色,此时绿色的对象代表着新创建的对象和上一次没有进行进行并发转移但是没有做对象重定位更新指针的对象,此时做重定位,绿色的指针此时指向的是老地址;

2)第二次垃圾回收的并发标记:

1)首先进行标记和GCROOTS间接关联的对象就是E,E此时是蓝色,此时一定是新创建出来的对象,此时将E表标记成红色;

2)做上一次垃圾并发回收转移对象的重定位,这个时候需要做绿色指针修正,绿色不是当前垃圾回收中可达性算法标记的对象的引用指针的颜色,此时从转发表中进行查询,然后将这条记录从转发表中删除掉,然后进行更新指针重定位,将绿色的指针变成红色,这个操作是原子的,删除转发表,做对象重定位更新指针地址也是做原子操作;

更新D对象的指针;

ZGC会把两次垃圾回收关联到一起,第一次垃圾回收的并发标记阶段是没有并发重映射的过程的,第二次垃圾回收的并发标记会对上一次并发转移的并发重定位,将该对象的颜色标记成本次ZGC垃圾对象标记的颜色;

ZGC:停顿时间非常低+分页模型+标识阶段(标识垃圾)+转移阶段(对象复制或者是移动)

四)读屏障:就是向应用程序中插入一小段代码的技术,从堆中读引用,惰性更新

读屏障是JVM向应用程序代码中插入一小段代码中的技术,判断引用是否已经更新过了,更新新地址,读写屏障代码只会在特定的代码JVM回收区域阶段来回调用;

1)当从堆中读引用的时候,就需要加入一个读屏障

2)判断当前指针是否是上一次本次GC的标记颜色

3)修正指针:对象重定位+删除转发表的记录

1)涉及到的对象:做并发转移但是还没有做并发重定位的对象

2)触发时机:在两次GC之间业务线程频繁访问这样的对象

3)读屏障:指针对象重定位+删除转发表记录+更新对象指针(两个操作是原子操作)

4)为什么要做读屏障?

对象已经被ZGC移动,但是指针没有进行修正,应用程序访问到指针旧地址出错,但是旧地址已经没有对象了

不是涉及到的代码都触发,吞吐量降低

读屏障=更新指针

1)在进行读取指针的时候会自动进行判断是否当前对象地址发生了变化,是通过类似于CAS来进行更新地址的,在使用到引用之前会更新地址;

2)ZGC的头部信息会记录着对应的对象是否被修改和是否是可达的

3)读屏障和写屏障都会进行判断是否在GC并发阶段进行赋值的,如果是并发阶段对引用做更新,所以此时再进行增量更新和记录原始快照,只有在这个阶段才会调用写屏障的代码去记录,正常程序执行过程中赋值操作太多了,每一次都调用读写屏障代码,效率非常低

4)那么在读屏障进行的时候,程序在读取引用地址的时候?是如何判断一个对象被挪动到了另一个区域里面了呢?

5)转发表:forwaord Table,在并发重分配的过程中,要挪对象

HashMap<老地址,新地址>

而G1垃圾回收器会产生STW,等到所有的对象都被挪走了,况且所有的引用都被更新了以后,才会继续执行业务线程,刷选回收之后,所有的对象都可以使用了

五)ZGC的触发时机:

1)基于分配速率的自适应算法:是最主要的GC触发方式,其算法原理可以近似认为ZGC根据最近的对象分配速率以及GC总时间,来进行计算当内存占用达到什么阈值的时候出发下一次GC,通过ZAllocationSpikeTolerance参数控制阈值大小,该参数默认2,数值越大,越早的触发GC,日志中关键字是“AllocationRate;

2)基于固定时间间隔;定时活动秒杀等场景

3)主动触发规则:时间间隔不确定

4)阻塞内存分配请求触发:

5)System.gc()触发

6)元数据分配触发:元数据区不足时导致,一般不需要关注MetadataGCThreshold

六)ZGC的优缺点:

1)ZGC优点:短停顿时间10ms,几乎实现了全程的并发执行

2)ZGC缺点:不支持32位系统,最大只能支持4TB内存容量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值