JVM学习日记⭐️ZGC的原理及设计思想(上)

1,082 阅读7分钟

“这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战

🔉引言

今天我们来肝ZGCZGC也是一款低延迟的垃圾收集器,由Oracle公司开发,ZGCShenandoah的目标是一样的,但二者的设计思路完全不一样,我们可以用一些专业术语来定义ZGC的特征:这是一款基于Region内存布局的,不设分代的,使用了读屏障、染色指针和内存多映射等技术来实现并发的标记整理算法的,以低延迟为首要目标的一款垃圾搜集器,可能大家听起来有些懵,没关系,我们慢慢介绍。

提到Region,你应该熟悉,前面的Shenandoah不也是基于Region的嘛,那二者有啥不同之处呢?ZGCRegion具有动态特性,可以动态创建、动态销毁,以及动态区域的容量大小,ZGCRegion可以具有大中小三种容量。

  • 小型Region:容量2M,可存放小于256kb的对象
  • 中型Region:容量32M,可存放256kb-4m的对象
  • 大型Region:容量动态可变,用于存放4M以上的对象,大小必须为2的整数倍,每个大Region只会存放一个大对象,且大Region不会重分配,因为复制大Region的成本非常高昂。

如图: image.png

并发整理

前面我们提到,Shenandoah的并发整理实现用到了转发指针和读屏障,那我们今天的主角ZGC也同样用到了读屏障,只不过设计思路上更为复杂精巧。

染色指针

以前,我们要记录一些额外的供虚拟机或者收集器使用的数据,就存储在对像头就好了,如分代年龄、锁记录、哈希码等,这种方式在对象能访问的情况下确实是很流畅的,但是要是对象不能访问了呢?比如对象被移动,或者我们就根本不希望访问对象也获取到一些额外的信息,那这个时候我们是没有办法做的。

那我们就可以思考,能不能从对象的指针上或者其他内存的地方获取这些额外的信息呢?比如我们前面提到的三色标记,就是标记对象的一些显著的特征,那标记在哪呢?标记在头上?标记记录在与对象相互独立的数据结构上?那看起来都可以,那哪个是ZGC采用的方案呢?

好像都没有,ZGC的染色指针是最直接、最纯粹的、直接把标记信息记录在对象的引用指针上,那为什么指针本身也能存储额外信息呢?那这就要从操作系统说起了,我们知道一个64位的系统所占的字节数是264次方,但实际上基于需求、性能、成本的考虑根本用不了那么多,比如64位的Linux操作系统也就支持47位的进程虚拟空间和46位的物理地址空间。

Linux64位的高18位不能用来寻址,剩余的46位所具有的64T的内存就被染色指针盯上了,将高4位提取出来记录4个标志信息,通过这个标记信息,虚拟机就能从指针中看到其引用对象的三色标记状态、是否重分配、是否通过finalize()方法才能够访问,那由于已经占了4位,那就导致ZGC能够管理的内存不超过4TB,图如下。

image.png

优势

虽然染色指针有4TB的内存限制,不能支持压缩指针,但是它带来的收益还是可观的,ZGC的设计者陈述了染色指针设计的三大优势:

其一就是你使用染色指针之后呢,一旦某个Region的存活对象被移动走后就可以立即回收释放,而不是像Shenandoah一样,要等到引用更新才能回收,至于为什么,稍后再解释。

其二就是大幅减少回收时的内存屏障数量,写屏障是记录一些对象的引用变动情况,如果把这个记录在染色指针上,那就用不到写屏障了,省去一部分内存屏障,对程序运行的效率而言是大有裨益的,所以ZGC对吞吐量的影响也特别低。

其三就是染色指针是一种可扩展的数据结构,这意味着它可以记录更多与对象标记、重定位过程的数据,Linux64位的指针还有前18位未使用,它们不能用来寻址,也可以开发它们用来记录,这样就可以腾出已用的4个标志位,将ZGC的最大堆内存扩展到64TB,也可以设置一些追踪信息,这样每次移动时,可以把低频次的访问对象移动到不常访问的内存区域。

存在的问题

那要顺利利用染色指针,你就必须想想,你这样随意更改内存中的某些指针中的几位,操作系统是否支持?处理器是否同意?这是个很现实的问题,就像你们相亲,你想和他们的漂亮菇凉走在一起,问问家长是否同意也是必要的。我们的程序也是一样的,因为程序最终都要转换成机器指令流由处理器运行,处理器可不管你哪些是标志,哪些是寻址的,它都会把整个指针当成一个内存地址执行,那ZGC的设计者是怎么解决的呢?那就是下面我们提到的内存映射

内存映射

提到内存映射,我们就不得不提远古时代的x86计算机,远古时代因为我们比较穷,所以只能让所有的进程都挤在一个内存空间里,那不同进程的内存自然无法相互隔离,那当一个进程污染了其他的进程后,那你就只能靠复位来恢复了。那这个问题怎么解决呢?

那从Intel80386处理器开始,就提供了保护模式用于隔离进程,此时处理器已经提供了分页管理机制把线性地址空间和物理地址空间区分开来,划分为大小相同的小块,这样的块叫做页。那我们就可以在线程虚拟地址页和物理地址页之间建立一个映射表,那这样就可以完成线程地址到物理地址的转换。

那映射呢,可以有一对一或者一对多或者多对一,这也需要根据实际进行设计,Linux/x86-64平台上的ZGC使用了多重映射(Muti-Mapping),将多个不同的虚拟内存地址映射到同一个物理内存地址上,这是一种多对一映射,将染色指针中的标志位看成是地址的分段符,然后把这些分段符都映射到同一个物理空间,经过多重映射转换后,就可以使用染色指针正常寻址了。如下图:

image.png

📝题外话

在20世纪,以及19世纪、18世纪,人们广泛认为知识和信仰之间存在着不可调和的矛盾。盛行于一些杰出人士中的观点认为,信仰应越来越多地被知识取代的时候已经到了;没有知识作为依托的信仰是迷信,因此必须加以反对。根据这一观念,教育的唯一功能就是打开通向思考和知识的通道,而学校作为人们进行教育的杰出机构,必须完全为这一目标服务。