ZGC是jdk11之后引入的新一代垃圾回收器。ZGC中对象空间分配是由对象分配管理器负责的,对象分配管理器的主要成员变量和成员函数,如下图所示。
ZObjectAllocator类图
我们先看一下对象分配管理器中的成员变量。
-
_used是一个模板类的变量,类型为ZPerCPU,它实质上是一个数组,元素类型为size_t,其内存示意图如下:
ZPerCPU内存示意图
在ZGC中支持多CPU同时进行对象分配,为了能准确地记录每个CPU已经分配的字节数,所以给每个CPU设置一个计数器,这其实也是为了减少竞争。
- _shared_medium_page也是一个模板类的变量,类型为ZContended,它存储的是页面指针。可以直接把它简化成一个页面的缓存,这里为什么要使用模板类?最主要的功能是为了实现对象的对齐。我们知道在现代计算机系统中硬件都支持缓存,如果按照缓存行大小对齐,则CPU在操作对象时速度更快,在ZGC中CPU缓存一般按照64位进行对齐。
- _shared_small_page是一个模板类的变量,类型为ZPerCPU,它实质上是一个数组,每个数组中的元素类型为页面指针。这个变量用于应用程序请求分配对象空间时使用。这里要注意的是所有的应用程序线程都从这个缓存中分配对象,但是为了加速对象的分配,按照CPU进行缓存。
- _worker_small_page是一个模板类的变量,类型为ZPerWorker,它实质上是一个数组,每个数组中的元素类型为页面指针。这个变量用于并行工作线程分配对象空间时使用。ZPerWorker这个模板类实现每个工作线程对应一个页面缓存,这也是为了最大限度地减少并发/并行(依赖于Worker的工作状态)时的竞争。
ZObjectAllocator最主要的成员函数是alloc_object。对象空间器中alloc_object方法的流程图如下:
这里再说明一下:
- 分配小对象时,会判断对象的请求来自哪里。如果来自于应用线程,所有的应用线程根据所在的CPU从共享的页面中分配对象空间。如果请求来自工作线程,则每个工作线程都有一个缓存的页面,优先从缓存的页面分配,不成功则分配新的页面。这样设计的主要目的在于使工作线程分配对象只发生在对象的并发转移中,多个缓存能加快对象的转移。
- 分配中等对象的时候,所有的中等对象都共享一个中等页面,也就是说该函数会被并发访问(可能是工作线程和应用程序线程并发执行,也有可能是多个应用程序线程之间并发执行),所以涉及竞争,需要额外的处理。ZGC中是先分配页面空间,再尝试设置新页面为共享页面,在设置过程中需要原子操作(通常在一个循环中处理),如果不能成功设置,说明有多个线程并发执行,且有其他的线程已经成功申请到新的页面,此时要释放多申请的页面。从这里可以看出,如果应用程序中含有大量中等对象,ZGC在空间分配时很容易发生页面申请竞争,导致性能下降。
- 对于大对象来说,ZGC不会在一个大页面中共享多个大对象,也就是说每个大对象都独占一个大页面,当然大页面的大小可能不相同(主要取决于对象的大小)。
- 还有一点,在上述的流程图中并没有体现,在ZGC中有一个参数ZStallOnOut-OfMemory,用于控制当发生OOM时终止程序还是等待垃圾回收器回收空间后继续运行。该参数在页面分配时使用
还要注意工作线程和应用程序线程竞争的情况,在工作线程转移对象的同时应用程序线程也转移对象(这和垃圾回收的设计相关,应用程序线程发现访问一个需要转移的对象,会先转移对象后访问)时,这种情况会出现,导致了竞争,会发生重复申请页面的释放。
网友评论