• 垃圾回收算法(6)三色标记


    GC目前的问题是,会暂停、阻碍代码的运行,即stop the world。增量式GC处理的就是这个问题。将GC变得可一阶段一阶段进行。
     
    分阶段运行的思路并不难,但具体要解决的问题其实是分阶段GC后,如何保证下次继续时,中断过程中引用关系的变化不会对GC造成影响
     
    三色标记法是一个逻辑上的抽象,将对象分成白:未搜索,灰:正搜索,黑:已搜索。
     
    在这里,和前面引用计数中提到的标色不一样,这里只是一个逻辑概念,在实现中并没有所谓的black, white。
     
    mark_sweep按增量来排,可以分成三个阶段:根查找、标记、清除
     
    incremental_gc() {
      case $gc_phase
        if GC_ROOT_SCAN
          root_scan_phase()
        if GC_MARK
          incremental_mark_phase()
        else
          incremental_sweep_phase ()
    }
     
    root_scan_phase() {
      for r : $root
        mark(r)
      $gc_phase = GC_MARK
    }
     
    mark(obj) {
      if !obj.mark
        obj.mark = true
        push(obj, $mark_stack)         // 理解下,不分段的GC中,由于是用递归方式直接深度搜索到底,所以不需要这个stack,而这个搜索过程目前会中断了,因此需要这样一个数据结构来记录。
    }
     
    上面这mark,就逻辑上把根对象由白标记为灰了。
     
    incremental_mark_phase() {
      for i : range 1..MARK_MAX          // 有个值,每次就处理这么多,可以有效防止stop the world
        if !is_empty($mark_stack)          // 以下栈中有值就取,无值就扫root
          obj = pop($mark_stack)
          for child : children(obj)
            mark(child)
        else
          for r : $root
            mark(r)
          while !is_empty($mark_stack) 
            obj = pop($mark_stack)
            for child : children(obj)
              mark(child)
     
      $gc_phase = GC_SWEEP            // 直接进入下阶段
      $sweeping = $head_start
      return
    }
     
    // 清除就不说了,同样思路,设置个最大值,每次只处理这么多。因为是mark_sweep,所以只要将未标记的引入free_list即可!!!!
     
    到这里遇到了关键问题:如果在垃圾回收阶段中间有新的对象引入,或是由于对象的指向关系,使得原本应该mark到的活动对象漏掉了,怎么办?这里会出现因为此对象没有mark而被清除的问题。
    新对象加入好说,对象的指向变化导致没有mark到,是这种情况:
    上图,C原先是应该被B递归搜索标记的。但在GC休息时,B不再指向C,C反而被A指向了。这个C在本轮就会被回收掉。
    这个问题是三色与mark之间的对应关系没有对应好导致。
    现在入mark_stack栈且mark与灰对应,搜索完成后,mark的是黑。而垃圾回收的依据,是mark过的对象,黑。而白,一定是非mark过,一定会被回收,但这里,白不应该被回收。因此,这个C对象的白色是错误的,要处理。
     
    wirte_barrier(obj, field, newobj) {
      if newobj.mark == FALSE            
        newobj.mark = true                    // 这里,因为本身write_barrier是一个赋值操作,因此此对象天生就被mark也算正常
        push(newobj, $mark_stack)         // 这个动作,就强行标记为灰了
     
      field = newobj
    }
    处理后,新引用的对象也是mark状态,是这样的:
     
    最后,如果新分配对象时,mark阶段已经完了,正在sweep,怎么处理?很简单,只要判断分配的对象在sweeping指针的前面还是后面。如果在前面已经sweep过的区域,直接忽略;如果在后面,简单mark下就可以。
     
    优点:
    1. 不会长时间停
     
    缺点:
    1. write_barrier略有开销
    2. 上面write_barrier会将对象强行制灰,也就是强行标记,是不大精确的,会造成当前轮次的垃圾残留。
     
    针对缺点2:
    场景是,write_barrier后,是对的。但再次回头,比如A又指向B了,那C这个垃圾在本轮就发现不了。
     
    改良型(steele)的write_barrier
    mark(obj) {
      if !obj.mark
        push(obj, $mark_stack)   // 和上面对表,少了mark = true
    }
     
    // 上面减少了mark的工作,将mark稳定到出栈处。这样可以引出下面的write_barrier
    // 这里,灰色已经不再是mark过,而是入过栈。反而,黑色才是mark过。
    write_barrier(obj, filed, newobj) {
      if $gc_phase == GC_MARK && obj.mark && !newobj.mark      // 逻辑也很清晰,不再一棍子将新加入的认为是非垃圾,而是认为“需要check是否垃圾”。如何check,就是将引用它的对象回滚成灰。
        obj.mark = false
        push(obj, $mark_stack)
     
      field = newobj
    }

    即:

     
    还有基于快照思想的一种write_barrier的思路:
    在write_barrier中入mark_stack栈的不是新对象,而是旧对象!这样,对于之前的对象的引用仍然存在,就不会丢对象。那么mark阶段中新生成的对象怎么处理?它直接将其mark,过于保守。
     
     
     
     
  • 相关阅读:
    About chrysanthemum and matrimony vine
    time stamp in javascript
    Feeling kind of the sorrow
    从零开始入门 K8s | Kubernetes API 编程利器:Operator 和 Operator Framework
    从零开始入门 K8s | Kubernetes API 编程范式
    Apache Flink 1.10.0 发布 | 云原生生态周报 Vol. 38
    回顾 | Kubernetes SIG-Cloud-Provider-Alibaba 首次网研会(含 PPT 下载)
    开发函数计算的正确姿势——运行 Selenium Java
    Serverless 解惑——函数计算如何访问 Mongo 数据库
    开发函数计算的正确姿势——使用 brotli 压缩大文件
  • 原文地址:https://www.cnblogs.com/qqmomery/p/6661574.html
Copyright © 2020-2023  润新知