zoukankan      html  css  js  c++  java
  • 垃圾回收算法(4)标记整理

    标记-压缩(整理)算法
     
    鉴于复制算法中,没有内存碎片的方式能大大提高分配效率,因此,在mark_sweep的基础上,也可以改良出mark_compact算法,使得空闲空间连续。同时又没有复制算法的无帮吃一半堆的问题。
     
    首先是最简单的算法,叫Lisp2算法
     
    在标记阶段,不变,仍然是对每个活动对象打上标签。
     
    随后,开始移动活动对象到堆的开头。因为没有两个堆,又要挪动对象放到一起,那么forwarding指针便需要在对象头中占用空间(复制算法中不用占用是因为它是非活动对象),且在移动前要做一轮forwarding指针的重指。还要做一轮子对象指针的重指。总体流程如下:
    compaction_phase() {
      set_forwarding_ptr()
      adjust_ptr()
      move_obj()
    }
     
    // 设forward指针最简单
    set_forwarding_ptr() {
      scan = new_addr = $heap_start
      while (scan < $heap_end)
        if (scan.mark == TRUE)
          scan.forwarding = new_addr
          new_addr += scan.size
        scan += scan.size
    }
     
    // 调整对象的成员指针,指向成员在堆中的新地址。对于全局变量,因为它们是根的子对象,也需要重指。
    adjust_ptr() {
      for (r : $roots)
        r = r.forwarding
     
      scan = $heap_start
      while (scan < $heap_end)                         // 这里为什么要扫堆,而不能从根直接递归遍历?因为直接递归可能会递归到不正确的已经重指过的值。直接遍历的话不会有顺序问题。
        if (scan.mark == TRUE)
          for (child : children(scan))
            child = child.forwarding
        scan += scan.size
    }
     
    // 最后,移动对象
    //不列了,比较简单,只需要扫堆,把活动对象向前移,清除forwarding和mark标记即可
     
    为什么移动对象要放在最后?因为必须先将所有对象的域指针指好才能动对象,否则如果对象先动了,那域指针就乱了
     
    优点:
    1. 堆利用效率高
    2. 无碎片
    3. 对象顺序未变
     
    缺点:
    1. 搜了3次堆,是标记清除算法的3倍
     
     
    针对以上缺点,有人基于partition算法(快速排序的第一步)引入了一个额外的限制,即对象大小整理成一致。提出了two-finger的算法。它只比Lisp2少扫一次堆。
     
    分成两步,此处直接描述。
     
    第一步,像两根手指分别从左右扫描,找出最右的活动对象和最左的非活动对象,把活动对象拷到左侧指针处。如此往复便规整了整个堆。第一步中有个重要的现象,是最终有效堆指针的右侧,全是“曾经的”活动对象,而移动后这块内存成了空闲堆。因此,可以将forwarding指针放在这些“已移走”的对象中,不需要占用头空间了。
     
    第二步,扫描全堆,调整对象的域指针,如果指向右侧一半的空堆,说明要调整,调整也只需要直接指向老地址的forwarding即可(老地址上老对象虽然移走,但forwarding指向了移走对象的目前地址)。并且,这次扫描不需要全堆,只要扫到分界线即可。
     
    优点:
    1. forwarding不用了
    2. 少扫堆一次
     
    缺点:
    1. 对象的顺序变了,缓存命中效率会小点
    2. 限制:所有对象大小要求一致
     
     
    最后,看另一个经典的整理算法:表格法
     
    它的思想是,相邻的活动对象一起移动,同时,以表格的形式记录下移动活动对象的起点,及向左移动的偏移量,如[100, 100]指从100开始的活动对象,整理时向左移动了100。问题是这数据记录在哪里?
    如果将连续的对象一起移动,那么堆中可以分为两种状态不同的间隔的内存块:活动对象内存块,垃圾内存块。只需要三个指针就可以维护当前的扫描进度关系,垃圾块开头,活动块开头,另一垃圾块开头。只需要将表格信息记录到当前的垃圾块中即可。当后续这块垃圾块被其他活动块移过来覆盖时,再将它移到后面的垃圾块中。
    这样,最后,就得到了顺序不变的活动对象仍然在堆的前部。后部是垃圾空间,同时包含着移动信息记录所在的表格。
    表格信息是用来更新指针的。
     
    因为移动后的对象中的域指针仍是旧值,我们可以遍历表格,从中找到这个旧值当初被移动了多少偏移量,从这个偏移量更新对象中的域指针。
     
    优点:
    1. 同样不需要多余空间存forwarding指针
    2. 同时又保持顺序
    缺点:
    1. 移动对象时,很可能也要移动表格
    2. 最终计算指针偏移时,要大量扫描表格
  • 相关阅读:
    485通讯和电力线载波通讯[收集转载]
    鹿城通综合业务系统常见问题
    485总表专题
    试试Live Writer的代码插件
    WPF路径动画——XAML篇
    无功电量的抄收
    一个JQuery操作Table的好方法
    季节计算脚本
    【学艺不精系列】关于Json.NET的反序列化
    NT6.x以上系统,多版本SQL Server局域网配置
  • 原文地址:https://www.cnblogs.com/qqmomery/p/6654254.html
Copyright © 2011-2022 走看看