zgc垃圾收集器原理

着色

zgc只支持64位系统,然后最大支持4T的堆内存,64位指针只需要使用42位就可以寻址4TB的空间,这意味着有多余的22位可以利用。zgc利用了4位,分别用来表示:是否已经finalize,重映射(remap),mark0,mark1。
mark0与mark1表示是否被标记,在gc周期性更换,这样可以不要重复去复原(就像以前survivor的复制回收算法,也就是这次用mark0表示,下次就用mark1,在用mark1标记时顺便把mark0复原,在用mark0标记时顺便把mark1复原)。

读屏障

zgc引入读屏障,也就是通过gc root对象读取其他堆对象时,触发读屏障,做一些事情,再读取。

分页

zgc将堆分为 2M(small), 32M(medium), n*2M(large)三种大小的页面(Page)来管理,根据对象的大小来判断在那种页面分配

gc过程

标记阶段

标记阶段将对象指针的remap位标记为未完成重定向并加入存活队列。
1.stop the world,标记所有gc root对象,将每个页上存活的gc root对象,保存在该页对应的存活表(bitmap来实现)。
2.恢复应用程序,进入并发递归标记所有对象阶段,这个阶段中,因为有读屏障的存在,脏的对象会再加入待标记队列,等待标记。
3.stop the world,并发递归标记结束,将脏队列遍历标记完

重定位阶段

1.并发的找出几个需要重定位的内存页
2.stop the world,将所有gc root对象重定位过去,重定位完成则指针的remap标记恢复为重定位完成
3.并发重定位
这个阶段为了提高效率,并且避免脏读取,会利用一个映射表,将旧地址映射到新地址。当出现脏读取时,会先判断是否当前指针已经被重定向,如果没有直接返回引用。否则代表重定向未完成,判断当前引用的地址是否在映射表里,如果没有代表还没加入映射表(在脏队列没有遍历到),直接修改remap标记为已完成(退出脏队列,避免被重定位)等待下一次的gc再来处理这个脏对象。如果在映射表里有是代表正在开启重定向,接着根据映射的地址判断是否已经完成重定位,如果是修改当前引用到新地址,并返回对象引用。否则执行relocate object,应用程序主动帮助将对象重定向到新地址并修改当前引用到新地址,返回对象引用。
并发重定位

下一次zgc标记

1.此时mark0和mark1交换,标记时复原上一轮使用到的标记位
2.清空重定向表

总结

zgc是充分利用多线程和大内存(zgc的分页会根据cpu核优先分靠近的内存),适合大堆和服务器多核的配置。因为大部分stop the world都是遍历gc root对象,所以暂停时间不长。对比g1的话,感觉是g1虽然是按照吞吐量来选择最优的region进行回收,但是其拷贝移动的过程还是要stop the world(因为g1只有写屏障没有读屏障),而这点zgc就有优势,其通过读屏障、remark标记和重定向表来并发拷贝非gcroot对象。并且zgc还根据新的linux系统有一些底层的优化,不得不吐槽还是得跟上版本(今天刚升级工程的springBoot到最新2.2.6,手动改好一个依赖升级问题的第三方框架——shiro-redis,因为好用但是停止维护好久了(–))。目前还在用jdk11,希望赶紧出新的LTS版本再升级jdk。
欢迎找歪歪梯聊骚

发布了37 篇原创文章 · 获赞 12 · 访问量 6319

猜你喜欢

转载自blog.csdn.net/weixin_44627989/article/details/105401474