一文读懂JVM垃圾回收器ZGC的设计及算法

从JDK11开始,java支持一种新的垃圾回收器-ZGC,号称STW在10ms之内,它到底有何神奇之处,今天带你来揭晓。

ZGC不同于以往的垃圾回收器,只能在64位的机器上使用ZGC,并且压缩指针会失效,这是由于ZGC使用了一种叫做着色指针的技术。并且ZGC能支持4TB(JDK13开始支持至16TB)。而且ZGC首次完全废除老年代新生代的概念。

ZGC的核心在于着色指针,但是其高效的秘诀绝不是简单的着色指针,主要秘诀如下:

1.ZGC进行了JIT编译器优化,能产生更少的垃圾

2.应用程序线程会对ZGC的处理过程提供一定的帮助(下文会提及)

3.使用了读屏障

4.支持NUMA(非一致性内存访问)

内存情况:ZGC前一款垃圾处理器叫G1,首次使用了region,并且把内存分成多个Eden两个survivor多个Old(每个内存块是一个region)。ZGC在这个的基础上更近一步,把内存分为多个region,但是不会限制这个region是Eden还是Old。

着色指针:ZGC的指针都是64位的,其中高18位未使用,中间4位就是着色指针,后面的44位是真实内存地址

读屏障:渡屏障是在JIT中增强的一小段代码,会在使用指针获取对象地址的时候先根据着色指针判断这个时候获取是否安全,如果不安全,会等上一会再返回新的对象地址(获取新的对象地址的方式下面会说到),读屏障只会在读取不安全的指针地址时使用,已经读取到的变量和非指针(基本数据类型)不会使用到读屏障。

ZGC的执行流程

1.开始标记(STW),找出根节点

2.并行标记,找出垃圾

3.处理边缘情况(STW)

4.重定位

5.重映射(这一步不算是单独的一步,一部分重映射会在下一次GC开始的时候和第一步一起执行,另一部分就是应用线程协助完成的)

流程详解:

1.第一次ZGC的时候,ZGC会STW,根据线程栈和常量池查找到所有的根节点

2.恢复应用线程运行,把根节点分配给GC线程,每个线程只标记自己负责的根线程

3.并发的去寻找可达对象,如果对象的某个属性是引用类型并且指针显示它还没有开始标记,会把指向该引用类型的指针置为开始标记

4.标记该对象完成后会把指针置为已标记完成的状态

5.当所有的对象都标记完成后,开始进行重定位,将对象A移到新的region中,并且把旧的地址和新的地址做一个映射(Forwarding Tables,A'->A ),注意:此时指向该对象的指针仍然是开始重定位状态,如果此时应用程序访问原本指向A的指针,会根据映射修改指针的地址为新的地址,并且修改指针状态为重定位完成,这里就体现了应用程序帮助ZGC完成GC过程的一方面。

6.重定位完成后整个GC过程基本完结。

7.第二次和以后的GC,在查到到根节点根据指针获取对象时,如果指针的状态是开始重定位状态,会根据Forwarding Tabels映射修改指针。

整个过程如下,里面可能有一些不太好理解的点,简单解答下:

1.对于Java和C不太了解的人可能不知道指针的概念,其实java访问一个引用对象都是获取到他的指针,需要访问该对象时,根据指针的地址去找到相应的对象,所以执行Person p = A.person的时候,p其实获取的是A.person这个指针,不会说出现A中的person指针被标记为已开始定位,还可以根据p访问person的情况,此时会发生读屏障。也不会发生指针状态为重定位完成,根据p访问到的是已释放内存的情况。

2.已释放的内存如果被重新分配,那么必然是一个新的指针,这个指针的状态也是初始化的状态,不会发生根据Forwarding Tables重新定位到其他地址的情况。

整个ZGC的处理流程是我根据openjdk提供的文档分析得到的,如果有错,欢迎大家指出!最后附件附上原始文档,有兴趣的小伙伴可以看看。

 https://files.cnblogs.com/files/fiftyonesteps/ZGC%E6%96%87%E6%A1%A3.zip

原文地址:https://www.cnblogs.com/fiftyonesteps/p/12568737.html