图解ZGC
简介
支持的版本
![](https://i-blog.csdnimg.cn/blog_migrate/773caf01d3069e86bf2f40b8d7de7ae2.png)
目标
![](https://i-blog.csdnimg.cn/blog_migrate/71999e73ea6fa8a1574c1aed9621d679.png)
jdk11版本最大可以支持4TB内存
jdk13最大可以支持16TB内存
最大停顿时间在10毫秒以内
吞吐量最多降低15%即假设不用ZGC需要1000毫秒才可以回收完
用ZGC最多需要1150毫秒可以回收完
这个影响不大 反正stw时间缩小了
暂时不分代
ZGC底层算法比较🐂,实现分代还比较麻烦,目前还未做到分代
现在还是简单的版本,后续可能会做到分代
即目前还未解决对象生命周期不一致的情况
分代的必要性
对象是有生命周期的
年轻代都是朝生夕死的对象
老年代都是一些老不死的对象
如果老年代和年轻代一样朝生夕死 那还得了
整个堆时时刻刻要做GC
ZGC内存布局
![](https://i-blog.csdnimg.cn/blog_migrate/c0768a738f6fdd5247fb4d9d8e875b7d.png)
和G1类似 都是分成一个一个小的内存区域
区别是没有分代的概念了
只有小型、中型、大型的区域区分
重要概念
NUMA
UMA 统一内存访问架构
所有的CPU都同时访问内存
那么就会涉及到内存的争抢
争抢失败会有锁竞争
即使使用CAS还是有损耗
但是使用UNMA会避免这种情况
![UMA](https://i-blog.csdnimg.cn/blog_migrate/b49c06ee002bfc6496732d2b3d2401ea.png)
多个CPU同时访问同一块内存
![](https://i-blog.csdnimg.cn/blog_migrate/f8485c865b759fbe724ea84ca27a152c.png)
每个cpu访问不同的内存块
每个内存块允许特定cpu具有优先访问权
避免了分配内存和使用内存资源争抢的问题
ZGC底层可以自动识别NUMA架构
ZGC运作过程
![](https://i-blog.csdnimg.cn/blog_migrate/af84d14fdba0916839d890d0d0dd1a5a.png)
gc过程最耗时的阶段是并发标记和并发清理
zgc将并发清理阶段分成了并发预备重分配、并发重分配阶段
![](https://i-blog.csdnimg.cn/blog_migrate/d781fdecffabbb8c3e9001c6f4adb922.png)
G1在筛选回收阶段是STW的
Shenandoah基于G1的 相当于G1的升级版本
G1中很多未实现的功能在Shenandoah中都有实现
比如Shenandoah就在筛选回收阶段实现了并发
并发标记
![](https://i-blog.csdnimg.cn/blog_migrate/758c8ca241a3d38ebe1869383e5fad26.png)
G1和CMS在并发标记阶段有一个三色标记
这些标记都在对象头的gc标记上
ZGC不会将标记信息放在对象内部
而是放在对象存储的内存空间那个指针上面
这就是颜色指针
![](https://i-blog.csdnimg.cn/blog_migrate/fcd3a9c18e25c056b28adcb51c216da4.png)
并发预分配
类比g1的计算回收收益比
看看哪些region需要回收
哪个region回收收益比高
计算好之后 zgc不是马上去清理 g1此时会马上清理
每次gc都会扫描所有的region
没有g1中的卡表(remember set 记忆集)的维护成本
![](https://i-blog.csdnimg.cn/blog_migrate/ee2c373f2cfa71e5408ea72706dd4f15.png)
并发重分配
将存活对象复制到另一块区域
那么这些对象的引用地址都变了
如果是g1的话筛选回收阶段 因为它是stw的
将对象复制过来之后 就会立马修改引用
但zgc这个过程是和用户线程并发执行的
所以不能直接将老的引用地址修改为新的
zgc是通过读屏障实现的
当读取到老的引用的之后 通过引用中的颜色指针判断引用是否变更
就会将新的引用替换掉老的引用
![](https://i-blog.csdnimg.cn/blog_migrate/d6046ef3c30747ccf2892b9ad06525f8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0e933f87d1665121c4356cdac056150b.png)
对象变更的几个点
-
并发标记时新增的对象
将这样的对象用黑色标记 下一次gc过程再回收
-
并发标记时老的对象状态变更
这部分对象在重新标记或最终标记的过程会修正
比如存活对象变成了垃圾对象就会讲gcroot引用链上剔除
-
将存活对象复制到新的region区
CMS 并发清理 ,过程中的新增对象用黑色标记 下次gc回收
G1 筛选回收 该过程stw 直接将对象老的地址更新为新的
ZGC 并发重分配 将新-老映射关系记录在转发表 用户线程访问访问老的对象时会通过转发表路由到新的地址
并发重映射过程
该过程就是将gcroot上老的对象引用更新为新的对象引用
这个过程实际上并没有做处理 而是将这个过程放在了下一次gc并发标记中去了
这样就节省了一次遍历对象图的开销
一旦所有指针被修正了之后 原来记录新旧对象关系转发表就可以释放了
颜色指针(染色指针)
![](https://i-blog.csdnimg.cn/blog_migrate/1b95ae3c3669dfd7fd36f5f7d917c15c.png)
颜色指针不可以被压缩
32位可以做指针压缩
总共64位 小于等于35位 即<=32G 可以做指针压缩 可以压缩到32位去存储
cpu寻址还是用的35位
真正使用的时候 还要解压缩变成35位才能够寻址到完整的32个g
cpu使用的是是35位 存到内存对象中压缩成32位了
jvm目前实现 最多35位之内的可以做指针压缩
zgc至少用42+4=46位 所以没有办法做指针压缩
读屏障
在并发重新分配阶段 将有效对象复制到另外一个空的region区中
原有的引用地址要更新 (惰性更新)
将从堆中拿老的对象值的时候
![](https://i-blog.csdnimg.cn/blog_migrate/e4e1aaeedbaa42fa766d61a8b535ce4a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ba138bb9f997ca265bed7ea163c25180.png)