回收算法的效率高标准才能使得程序高效的运行,这个章节主要记录一些实现高效执行的算法实现 细节,以便于了解不同的垃圾回收器。

GCRoots根节点枚举

可作为GCRoots的节点主要是全局性的引用,比如常量、静态属性、虚拟机栈中的本地变量表等,以这个作为根节点然后向下去找引用链。现在java应用程序越来越大,比如单方法区的大小就成百上千兆再加上类、常量等,如果把一这里为起源的引用全部检查一遍耗时可想而知工作量是相当的大。
可达性分析中根节点枚举是需要停止用户线程的,而查找引用链的过程是可以和用户线程一起并发执行的,但是根节点枚举还是需要保证在一个一致性的快照中执行,需要保证在分支中不会出现根节点集合的对象引用还会出现变化的情况从而保证正确性。
当用户线程停止查找引用链的时候,并不需要我们把执行上下文和全局的引用都检查一遍,虚拟机应该知道哪里有存放对象引用。在HotSpot解决方案中是通过OopMap来实现的,当类加载器加载完成后就会把对象内什么偏移量上对应的数据类型计算出来,收集器在扫描过程就可以直接从中知道这些引用信息了也就不需要从方法区那些根节点集合中一个一个的去检查了。

安全区

我们使用OopMap来存储引用信息来解决了引用链查找时间的问题,但是OopMap的内容会受到不同指令的影响发生改变,那么我们必须在一个特定的环境下才记录这些信息并开始垃圾回收以保证一致性。
成为安全点的特征必须是能让程序长时间的执行,安全点太短的话会导致增大运行时的内存符合,安全点太长会导致等待时间过长,具有能让程序长时间执行的指令包括 调用方法、循环跳转、异常跳转。
如何让程序在安全点都停顿下来有两种方法

  1. 抢先式中断
    1. 不需要代码配合,在垃圾收集发生时系统把所有用户线程中断如果发现哪个线程没有到达安全点就让那个线程继续执行直到安全点。
  2. 主动式中断

    1. 当垃圾收集需要中断时,不主动去中断线程,而是设置一个标识,各个线程执行时会去不断的轮询这个标识如果发现这个标识为真时就主动在附近的安全点中断挂起。这个轮询的标识和安全点是重合的。

      安全域

      安全域所解决的问题是,在程序不执行的情况下(程序处于sleep状态或者blocked状态)没有获取到处理器的执行时间线程无法响应虚拟机中断请求,无法走到安全点挂起,虚拟机也不会等到线程被激活获取到处理器的处理时间。
      安全域保证在一段代码片段中引用关系不会发生变化,只要在这个安全域内的任何地方开始回收都是安全的,在进入安全区是会标识在即已进入安全区,这时候发生垃圾收集就不用管这些已经进入安全域的线程了,当要离开安全域时会检查虚拟机是否已经完成了根节点枚举,完成则离开否则需要继续等待,知道等到完成枚举信号后才能离开。

      记忆集与卡表

      记忆集与卡表主要解决的是跨代引用的问题避免GcRoots扫描范围过大,只要涉及到部分区域收集都会发生跨代引用的问题。记忆集中记录了非收集区域指向收集区域的引用指针的信息集合的这样一种数据结构。
      记忆集的三种实现方式:
  3. 字节精度

    1. 记录精确到机器字长(处理器寻址位数,32或64,这个精度确定了机器访问物理内存地址的指针长度)包含了跨代指针。
  4. 对象精度
    1. 记录精确到每一个对象,这个对象里有属性包含了跨代指针。
  5. 卡精度

    1. 记录精确到一块内存区域,该区域内有对象含有跨代指针(常用到的)。
    2. 卡表是记忆集的一种实现,类似于Map和HashMap的关系,HotSpot通过一个字节数组实现卡表,数组中的每一个元素都对应一块内存被称之为卡页,大小是2的N次幂,一个卡页包含了N多的对象,如果这个区域中有跨代引用就会将此元素表示为1表示变脏 正常为0,变脏的元素对应的内存就需要加入到GCRoots扫描。

      写屏障

      写屏障解决的是程序在编译期代码已经变成了机器指令流时在机器码层面上对卡表进行维护的问题。写屏障存在伪共享问题

      并发的可达性分析

      前面我们可达性分析算法分析出哪些区域的内存是需要扫描的,这些对象是以图的形式存在 也叫对象图,需要对对象图中的对象进行遍历访问,那么我们怎么标记哪些对象是访问过哪些没有访问过呢?
      三色标记发:
  6. 白色

    1. 表示对象尚未被访问过,发生在可达性刚刚开始的时候,所有的对象都是白色的,若在分析结束后对象还是白色则表示不可达。
  7. 灰色
    1. 表示对象已经被垃圾收集器访问过了,但是这个对象至少还有一个引用没有被扫描。
  8. 黑色
    1. 表示对象已经被垃圾收集器访问过了,这个对象的所有引用都已经被扫描过了并且是安全存活的,其他的对象的引用如果指向了黑色对象无需重新扫描。

并发问题
随着垃圾收集器的不断发展,现在垃圾收集线程和用户线程可以并发执行同时修改对象引用,那么就避免不了出现错误标记的情况,把存活对象标记为失效对象。解决这个问题有两种方案。

  1. - **增量更新**
  2. - 黑色对象插入新的指向白色对象引用关系时进行记录等到扫描结束后,再以记录的黑色对象为根重新再扫描一次。
  3. - **原始快照**
  4. - 灰色对象要删除指向白色对象的引用时进行记录等到扫描结束后再以记录的灰色对象为根再扫描一次。