为了支持TB级内存,设计了基于页面 page的分页管理,类似于G1的分区Region
为了能够快速地并发标记和并发移动,对内存空间重新进行了划分,这就是ZGC 引入的颜色指针Color Pointers;
同时ZGC为了能更加高效地管理内存,设计了物理内存和虚拟内存两级内存管理
2.1 操作系统地址管理
程序试图访问一个虚拟内存页面时,这个请求会通过操作系统来访问真正的内存;也就是通过内存管理单元MMU把页码转换成页框码frame,加上虚拟地址提供的页内偏移量形成物理地址后去访问物理内存;如果没有记录,就判断该虚拟地址是否有效,如果是有效的,就从虚拟内存中将该地址指向的页面读入内存中的一个空闲页框中,并在页表中添加相对应的表项。
如果内存中没有空闲物理页框,就启动 “交换”,调用相应的内核操作函数,在物理页框中寻找一个当前不在使用的页面占据的页框,然后换成新的页面。
2.2 ZGC内存管理
1 实现两级内存管理;ZGC重新定义了虚拟内存和物理内存的映射关系。
2 ZGC 使用颜色指针Color Pointers来区别不同的虚拟视图,这些视图都会映射到操作系统的同一物理地址。
3 0~4TB : Java 堆空间(最大堆空间) 4~8TB 对应Marked0 视图, 8~12TB 对应Marked1 视图, 16~20TB 对应Remapped 这3个视图也是Java 堆空间的虚拟视图,这3个空间的切换是由垃圾回收的不同阶段触发的
4 ZGC 的低42位 用于描述真正的虚拟地址, 42~45位 用于描述元数据,其实就是颜色指针Color Pointers;46位不用
47~63位 固定为0
2.2.1 多视图映射 : 多个虚拟地址映射到一个物理内存地址,虚拟地址和物理内存直接的关联由ZGC来管理
2.2.2 ZGC 多视图映射
ZGC 仅 支持 Linux 64位系统,ZGC是通过系统调用mmap完成地址多视图映射,多次调用mmap 不同的虚拟地址,步骤如下
(1) 创建并且打开一个文件描述符,可以是内存文件描述符,也可以是普通文件描述符,在JVM启动时完成。
必须先syscall 从用户态进入内核态,然后再创建文件描述符
(2) 分配内存的时候,新分配的虚拟地址转化为Marked0,Marked1和remapped 的虚拟地址,再使用mmap映射到这个文件描述符
2.2.3 页面设计
和G1一样,内存划分成小的分区,称为页面。小页面 2MB,中页面 32MB,大页面是操作系统控制的
小页面优先回收,中页面,大页面尽量不回收
2.2.4 对NUMA的支持
NUMA : 为了避免各个cpu对内存带宽的争抢访问,造成性能瓶颈。就把CPU和内存集成在一个单元上,这就是非统一内存访问 NUMA,CPU访问本地存储器速度会比非本地存储器要快一些。
可以把多个CPU集成在一个节点上,优先访问本地的内存。
ZGC 是支持NUMA的,进行小页面分配时会优先从本地内存分配,不能分配时才会从远端的内存分配。中大页面,由操作系统分配,不支持NUMA
2.2.5 ZGC中的物理内存管理
ZGC 物理内存基本单位是段,大小为2MB,也是最小粒度。设计成一个连续的地址结构
2.2.6 ZGC 中的虚拟内存管理
小页面ZGC会从 虚拟空间的头部开始分配,中页面,大页面ZGC会从虚拟空间的尾部开始分配。因为中大页面分配和释放的成本很高
2.2.7 ZGC 内存预分配
ZGC 启动时会预先分配一部分内存,初始化成功后,这内存可以直接使用。应用程序请求内存时,优先从预分配的空间中分配
2.2.8 ZGC 对象分配管理
分配内存,先看是否支持TLAB,如果支持,就从TLAB中分配内存。对象空间是从页面中请求的,页面足够大,就能很快分配,页面相当于缓存。
2.3.1 对象空间分配
由对象分配管理器负责
1.如果大于4MB,为大对象,则由操作系统分配大对象空间,每个大对象独占一个大页面
2. 大于256KB,小于4MB,为中等对象,所有中等对象会共享一个页面,中等对象很多,ZGC空间分配时很容易发生页面申请竞争,导致性能下降
3.小于256KB 为小对象,如果对象来自于应用线程,应用线程就从所在的CPU 的共享页面中分配空间。如果请求来自于工作线程,则每个工作线程都有一个缓存的页面,优先从缓存的页面分配。不成功则分配新的页面
2.3.2 页面分配
对象空间分配的时候,页面实际上作为缓存使用。不成功则从预分配的内存中分配,还不成功,则释放预分配未使用的内存和部门页面缓存的内存,组成更大的空间用于分配。