2.2.2 ZGC多视图映射
由于ZGC仅支持Linux 64位系统,所以可以想象ZGC是通过系统调用mmap来完成地址多视图映射的。ZGC实现多视图映射的过程和2.2.1节基本一致,步骤可以总结如下:
❑创建并打开一个文件描述符,这个文件描述符可以是内存文件描述符,也可以是普通文件描述符(最好是内存文件描述符,其性能更高)。创建并打开文件描述的动作是在JVM启动时完成的,简化的流程图如图2-4所示。
图2-4 ZGC初始化多映射视图用到的文件描述符
这里有一个小知识点,创建内存文件描述符通过系统调用memfd_create函数完成,而memfd_create函数是内核态才能调用的函数,所以必须通过syscall函数从用户态进入内核态,并传递参数__NR_memfd_create,最终操作系统调用相应的函数完成。另外需要注意的是在初始化过程中,如果不能创建文件描述符,将导致初始化失败,JVM则不能启动。保存文件描述符的数据结构如图2-5所示。
图2-5 ZPhysicalMemoryBacking类结构关键成员
我们可以看到在ZBackingFile中有一个成员变量_fd,该成员变量用于保存上面所提到的文件描述符。在ZBackingFile中还有一个成员函数try_expand,用于扩大共享内存对象的大小,成员函数create_mem_fd和create_file_fd分别是创建内存文件描述符或者基于文件系统的文件描述符,它们在图2-4中创建文件描述符时被调用。在ZPhysicalMemoryBacking中,成员变量_manager是分配内存管理器,其作用是分配或者回收内存,具体内存分配在2.2.5节介绍;成员函数map用于完成多视图映射。
❑在分配内存的时候,新分配的虚拟地址转化成3个映射视图(Marked0、Marked1和Remapped)中的虚拟地址,再使用mmap映射到这个文件描述符上。
映射方法和2.2.1节中采用的稍有不同,在2.2.1节的例子中,我们使用mmap时并未指定虚拟地址,而是由Linux自动分配一个虚拟地址。在ZGC中明确地指定了3个映射视图中的虚拟地址。这个代码片段就是上面我们提到的ZPhysicalMemoryBacking中的成员函数map,它的实现非常简单,我们直接看一下源码,如下所示:
void ZPhysicalMemoryBacking::map(ZPhysicalMemory pmem, uintptr_t offset) const { if (ZUnmapBadViews) { // 在调试参数ZUmmapBadViews为true时,只把地址映射到正在使用的地址视图中 // 此时如果访问其他地址视图,将导致内存访问故障 map_view(pmem, ZAddress::good(offset), AlwaysPreTouch); } else { // 把地址映射到3个视图中,根据垃圾回收进行的阶段自动选择地址视图 map_view(pmem, ZAddress::marked0(offset), AlwaysPreTouch); map_view(pmem, ZAddress::marked1(offset), AlwaysPreTouch); map_view(pmem, ZAddress::remapped(offset), AlwaysPreTouch); } }
这里简单解释一下上面代码中map_view的3个参数,第1个参数传递的是ZGC关于物理内存的一个结构体,实际上这里使用的是物理内存的大小;第2参数是虚拟地址,这里会把应用程序使用的[0~4TB)虚拟地址转化成Marked0、Marked1和Remapped视图对应的虚拟地址,其处理方法非常简单,把最低42位的地址分别与第42~44位进行“位或”运算,即得到不同视图里面的虚拟地址。
目前ZGC并不支持Windows平台,这会影响ZGC的普及。在2.2.1节我们提到Windows系统上也可实现多视图映射功能,而且Windows系统也有把多个指定的地址映射到一个物理地址上的API(MapViewOf FileEx),这里不再赘述,具体可以参考Windows系统编程。