这个架构被称为 SMP (Symmetric Multi-Processor),因为任一个 CPU 对内存的访问速度是一致的,不用考虑不同内存地址之间的差异,所以也称一致内存访问(Uniform Memory Access, UMA )。
这个核心越加越多,渐渐的总线和北桥就成为瓶颈,那不能够啊,于是就想了个办法。
把 CPU 和内存集成到一个单元上,这个就是非一致内存访问 (Non-Uniform Memory Access,NUMA)。
简单的说就是把内存分一分,每个 CPU 访问自己的本地的内存比较快,访问别人的远程内存就比较慢。
当然也可以多个 CPU 享受一块内存或者多块,如下图所示:
但是因为内存被切分为本地内存和远程内存,当某个模块比较“热”的时候,就可能产生本地内存爆满,而远程内存都很空闲的情况。
比如 64G 内存一分为二,模块一的内存用了31G,而另一个模块的内存用了5G,且模块一只能用本地内存,这就产生了内存不平衡问题。
如果有些策略规定不能访问远程内存的时候,就会出现明明还有很多内存却产生 SWAP(将部分内存置换到硬盘中) 的情况。
即使允许访问远程内存那也比本地内存访问速率相差较大,这是使用 NUMA 需要考虑的问题。
ZGC 对 NUMA 的支持是小分区分配时会优先从本地内存分配,如果本地内存不足则从远程内存分配。
对于中、大分区的话就交由操作系统决定。
上述做法的原因是生成的绝大部分都是小分区对象,因此优先本地分配速度较快,而且也不易造成内存不平衡的情况。
而中、大分区对象较大,如果都从本地分配则可能会导致内存不平衡的情况。
Using colored pointers染色指针其实就是从 64 位的指针中,拿几位来标识对象此时的情况,分别表示 Marked0、Marked1、Remapped、Finalizable。
我们再来看下源码中的注释,非常的清晰直观:
0-41 这 42 位就是正常的地址,所以说 ZGC 最大支持 4TB (理论上可以16TB)的内存,因为就 42 位用来表示地址。
也因此 ZGC 不支持 32 位指针,也不支持指针压缩。
然后用 42-45 位来作为标志位,其实不管这个标志位是啥指向的都是同一个对象。
这是通过多重映射来做的,很简单就是多个虚拟地址指向同一个物理地址,不过对象地址是 0001.... 还是0010....还是0100..... 对应的都是同一个物理地址即可。
具体这几个标记位怎么用的,待下文回收流程分析再解释。
不过这里先提个问题,为什么就支持 4TB,不是还有很多位没用吗?
首先 X86_64 的地址总线只有 48 条 ,所以最多其实只能用 48 位,指令集是 64 位没错,但是硬件层面就支持 48 位。
因为基本上没有多少系统支持这么大的内存,那支持 64 位就没必要了,所以就支持到 48 位。
那现在对象地址就用了 42 位,染色指针用了 4 位,不是还有 2 位可以用吗?
是的,理论上可以支持 16 TB,不过暂时认为 4TB 够了,所以暂做保留,仅此而已没啥特别的含义。
Using load barriers在 CMS 和 G1 中都用到了写屏障,而 ZGC 用到了读屏障。
写屏障是在对象引用赋值时候的 AOP,而读屏障是在读取引用时的 AOP。
比如 Object a = obj.foo;,这个过程就会触发读屏障。
也正是用了读屏障,ZGC 可以并发转移对象,而 G1 用的是写屏障,所以转移对象时候只能 STW。
简单的说就是 GC 线程转移对象之后,应用线程读取对象时,可以利用读屏障通过指针上的标志来判断对象是否被转移。