Mark Compact GC (Part two :Two-Finger)

目录

  • Two-Finger算法

    • 前提
    • 概要
    • 步骤一:移动对象
    • 步骤二:更新指针
    • 优缺点
  • 表格算法
    • 概要
    • 步骤一:移动对象群 和 构筑间隙表格
      • 移动对象群
      • 构筑间隙表格
    • 步骤二:更新指针
    • 优缺点

Two-Finger算法

Robert A.Saunders 对堆执行两次搜索

前提

Two-Finger 算法,必须将所有对象整理成大小一致。它没有在对象的头中设立forwarding指针,而是在对象的域中设立forwarding指针即可。

概要

Two-Finger算法由一下两个步骤构造。

  • 移动对象
  • 更新指针

在Lisp2算法中,是将对象移动到堆的一端。在Two-Finger中,操作对象向左滑动,通过执行压缩算法来填补空闲空间。此时为了让更好的填补空间,所以对象大小必须一样。

移动前的对象都会被保留(图的白色对象)。因为在Two-Finger算法中,我们要利用放置非活动对象的空间来作为活动对象的目标空间,这是为了让移动前的对象不会在GC过程中被覆盖掉。这样一来,我们就能把forwarding指针设定在这个移动前 的对象的域中,没有必要多准备出 1 个字了。

步骤一:移动对象

  • $free和live两个指针,从两端向正中间搜索堆。
  • $free是用于寻找非活动的指针,live是寻找活动对象(原空间)

  • 两个指针发现空间和原空间的对象时会移动对象。

  • 途中虚线其实表示forwarding。
  • 之后使用move_obj函数对对象进行移动其伪代码如下:
move_obj(){
    $free = $heap_start
    live = $heap_end - OBJ_SIZE
    while(TRUE)
        while($free.mark == TRUE) //从前往后寻找非活动对象
            $free += OBJ_SIZE
        while(live.mark == FLASE) // 重后往前 寻找活动对象
            live -= OBJ_SIZE
        if($free < live)   // 判断交换条件
            copy_data($free, live, OBJ_SIZE)
            live.forwarding = $free
            live.mark = FALSE
        else
            break
}
  • 先从前往后,使用$free寻找非活动对象。
  • 在从后往前,使用live寻找活动对象。
  • 找到之后,判断两者位置。如果非活动对象在活动对象之前,就执行复制操作。否则就退出循环。

步骤二:更新指针

接下来寻找指向移动前的对象的指针,把它更新,使其指向移动后的对象。更新指针操作的是adjust_ptr()函数。

  • 当对象移动结束时,$free 指针指向分块的开头,这时位于 $free 指针右边的不是非活动对象就是活动对象。
  • $free右边地址的指针引用的是移动前的对象。
adjust_ptr(){
    for(r :$roots)
        if(*r >= $free)
            *r = (*r).forwarding

    scan = $heap_start
    while(scan < $free)
        scan.mark = FALSE
        for(child :children(scan))
            if(*child >= $free)
                *child = (*child).forwarding
            scab += OBJ_SIZE

}
  • 先查询根直接引用的对象。当这些指针的对象在$free右边的时候,就意味这个对象已经被移动到了某处。在这种情况下必须将指针的引用目标更新到移动后的对象。
  • 所有活动对象都在$heap_start 和 $free之间,我们需要取遍历这一部分堆。

优缺点

优点:Two-Finger 算法能把 forwarding 指针设置在移动前的对象的域里,所以不需要额外的内存 空间以用于 forwarding 指针。只需要2次搜索堆

缺点: Two-Finger 算法则不考虑对象间的引用关系,一律对其进行压缩,结果就导致对象的顺序在压缩前后产生了巨大的变化。因此,我们无法更好的使缓存。 对象大小必须一样

表格算法

B.K.Haddon 和 W.M.Waite, 1967

这个算法使用表格来进行压缩,和Two-Finger一样都是执行两次压缩。

概要

表格算法通过以下2个步骤来执行压缩。

  1. 移动对象以及构筑间隙表格(break table)
  2. 更新指针

步骤1是让连续的活动对象群一并移动。(和前面所接触到的压缩算法都不同)。除此之外还要预留更新指针所用到的信息,这里我们使用间隙表格。

间隙表格,大概意思是“按照一个个活动对象群记录下压缩所需要的信息的表格”。这个表格事先放入移动前的对象群信息(位于对象群的首地址和较低地址的分块的总大小)。为了方便地址计算,我们将1个字的大小定为50.如下图示:

  • 各个入口左边的值是活动对象群的首地址,右边的值是分块的总大小。随着对象的移动,它会被放置在空闲空间里。不过,间隙表格的各个入口需要2个字节。也就是说,这算法的其中一个限制条件就是,每个对象都必须在2个字节以上。
  • 步骤2,更新每个指针。

步骤一:移动对象群 和 构筑间隙表格

移动对象群

活动对象群移动前和移动后(move_obj())的状态如图示:

move_obj(){
    scan = $free = $heap_start
    size = 0
    while(scan < $heap_end)  // while 循环 1
        while(scan.mark == FALSE)
            size += scan.size
            scan += scan.size
        live = scan
        while(scan.mark == TRUE) // while 循环 2
            scan += scan.size
        slide_objs_and_mark_bt(scan ,$free, live, size)
        $free += (scan-live)
}
  • scan用于寻找活动群对象,从堆头开始搜索。
  • $free是指向对象群目标空间的指针,size是保持分块大小的变量。这里的分块是指用来记录到间隙表格里的分块。
  • 第一个while中,scan指针负责寻找活动对象的群的开头。也就是说,直到它寻找到活动对象为止,都会跳过非活动对象。于此同时使用size计算scan指针跳过的空间大小。
  • scan是指针,size是整数。
  • 搜索结束时候,scan指针指向活动对象群开头。这个为止记录在live指针里。
  • 之后继续使用scan,搜索连续的多动对象群。(一次找对活动对象循环完毕)

这时,堆的状态如下图示

在第二个while循环中

  • 其中slide_objs_and_make_bt()函数中执行活动对象群和构筑间隙表格的操作。对象群的原空间是live,目标空间是$free,要移动的对象群的总大小是scan-live。
  • 最后一行,准备下一次移动,将$free移动scan-live个大小,即$free向后移动大小等于对象群的大小。如下图示:

  • 这里和Lisp2算法一样,都是通过把活动对象左滑压缩。不过这里是移动连续的对象群。

构筑间隙表格

在上一个图中,每次移动对象群的时候都需要吧信息注册到间隙表格中。注册入口是对象群的首地址live对象群滑动大小size的组合。如下图示:

构筑间隙表格是在slide_objs_and_mark_bt()方法中指向的,下面使用图例来说明过程:

  • 间隙表格构筑有以下两项操作构成。

    • 移动对象群
    • 移动间隙表格
  • 下图示()内数字表格各个对象的首地址,设1个字的大小为50

  • 如中a部分,在移动对象群BC的同时构筑间隙表格。将BC的首地址100以及BC最左边的分块大小100组合成一对,通过scan指针写入已知分块的350号地址。
  • b部分,在这里进行的是移动对象群FG的操作。这时候要注册到间隙表的的信息是(550, 300)不过不能直接将该信息写入(100, 100)之后(450块)因为对象群要移进去。
  • 这时候,我们有的间隙表格移动到FG后面也就是700号地址里。
  • 完成后状态如e所示。注意间隙表格的入口顺序,各个入口不是按入口里的第一元素排列的,也就是说,不是按活动对象群的首地址live进行排列的。
    • 在b中,因为间隙表格妨碍到对象FG的移动,所以先让它回避到800,之后在移动FC将新的表格注册到了700上。
    • 像这样往已有的间隙表格中新追加入口时,会有表格左侧空闲的情况,在这种情况下,入口顺序只能乱了。
    • 当然也可以按照顺序排列,如论是在添加的时候按顺序,或者添加完之后排序,它都要花费一定的空间和时间。
  • 因为没有按照第一元素live的顺序排列,所以增大了更新指针的计算量。

步骤二:更新指针

在dajust_ptr()函数中,将引用移动前的对象的指针全部换成引用移动后的对象的指针。这项操作本身和前面的两个算法中的操作是相同的。

adjust_ptr(){
    for(r :$roots)
        *r = new_address(*r)

    scan = $heap_start
    while(scan < $free)
        scan.mark = FALSE
        for(child : children(scan))
            *child = new_address(*child)
        scan += scan.size

}


下面是new_address(obj)函数

new_address(obj){
    best_entry = new_bt_entry(0, 0)
    for(entry :break_table)
        if(entry.address <= obj && $best_entry.address < entry.address)
            best_entry = entry
    return obj - best_entry.size

}
  • 这个函数返回参数obj移动后的地址,在其中new_bt_entry(0, 0)函数中生成虚拟间隙表格的入口。
  • for循环负责调查间隙表格,在持有obj及其一下地址的入口中寻找最大的入口。这样一来就得到了持有obj所属对象群信息的入口。这个入口就是best_entry
  • 如果间隙表格里的入口是按照地址顺序整齐排列的,我们就有可能用二分查找有效地址查询到best_entry。但是间隙表格的入口并不是整齐排列的,因此就需要通过上面这种方式来查找。
  • best_entry是一个入口,这个入口持有obj所属对象群移动前的信息。属于这个对象群的对象都会被向左移动best_entry.size个大小。因此obj移动后的地址变成了obj-best_entry.size。

上图中如果我们想知道B移动到了B?,首先就要以B的地址100为线索调查间隙表格,然后就会发现入口(100,100)是best_entry,接下来可由B的地址 100 求得 best_entry.size,即将 B 的地址减去 100 得到 B? 的地址 0。 同理,我们可以从 F 的地址 550 减去入口(550,300)中的 300,得到 F? 的地址 250。

优缺点

优点:算法很好地利用了分块,保留了更换指针所必要的信息。(没有为压缩备出多余空间,)并且它没有改变对象的顺序,所以可以通过缓存来提高对象的访问速度。

缺点:维持间隙表格需要付出很高的代价,每次移动对象群都要对表格进行操作。

原文地址:https://www.cnblogs.com/Leon-The-Professional/p/9994395.html

时间: 2024-08-03 18:28:51

Mark Compact GC (Part two :Two-Finger)的相关文章

Mark Compact GC (Part one: Lisp2)

目录 什么是GC 标记-压缩算法 Lisp2 算法的对象 概要 步骤 步骤一:设定forwarding指针 步骤二:更新指针 步骤三:移动对象 优缺点 什么是GC 标记-压缩算法 需要对标记清除和GC复制算法有一定了解 GC标记-压缩算法是由标记阶段和压缩阶段构成. 标记阶段和标记清除的标记阶段完全一样.之后我们要通过搜索数次堆来进行压缩. Lisp2 算法的对象 Donald E.Knuth 对象结构如图示: Lisp2 算法在对象头中为forwarding指针留出空间,forwarding指

GC标记-清除算法(Mark Sweep GC)

世界上第一个GC算法,由 JohnMcCarthy 在1960年发布. 标记-清除算法由标记阶段和清除阶段构成. 标记阶段就是把所有的活动对象都做上标记的阶段. 标记阶段就是"遍历对象并标记"的处理过程. 标记阶段经常用到深度优先搜索. 清除阶段就是把那些没有标记的对象,也就是非活动对象回收的阶段. 清除阶段collector会遍历整个堆,回收没有打上标记的对象(即垃圾). 内存的合并操作也是在清除阶段进行的. 分配 分配指将回收的内存空间进行再利用. -> 伪代码实现内存分配

几种垃圾回收GC概述

垃圾回收机制 引用计数回收器(Reference Counting Collector) 原理是在每个对象内部维护一个整数值,叫做这个对象的引用计数,当对象被引用时引用计数加一,当对象不被引用时引用计数减一.当引用计数为 0 时,自动销毁对象. 目前引用计数法主要用在 c++ 标准库的 std::shared_ptr .微软的 COM .Objective-C 和 PHP 中. 计数器表示资源(内存中的对象)的使用次数,当计数器变成零的时候就可以将该资源销毁.在向已有的系统添加垃圾回收器时,开发

HotSpot JVM and GC basics study note

Hotspot JVM and GC basics study note JVM components HotSpot JVM comprises three main components: the class loader, the runtime data areas and the execution engine. Key JVM components There are three key components related to tune performance: the hea

[译]GC专家系列1: 理解Java垃圾回收

原文链接:http://www.cubrid.org/blog/dev-platform/understanding-java-garbage-collection/ 了解Java的垃圾回收(GC)原理能给我们带来什么好处?对于软件工程师来说,满足技术好奇心可算是一个,但重要的是理解GC能帮忙我们更好的编写Java应用程序. 上面是我个人的主观的看法,但我相信熟练掌握GC是成为优秀Java程序员的必备技能.如果你对GC执行过程感兴趣,也许你只是有一定的开发应用的经验:如果你仔细考虑过如何选择合适

Java GC专家系列1:理解Java垃圾回收

了解Java的垃圾回收(GC)原理能给我们带来什么好处?对于软件工程师来说,满足技术好奇心可算是一个,但重要的是理解GC能帮忙我们更好的编写Java应用程序. 上面是我个人的主观的看法,但我相信熟练掌握GC是成为优秀Java程序员的必备技能.如果你对GC执行过程感兴趣,也许你只是有一定的开发应用的经验:如果你仔细考虑过如何选择合适的GC算法,说明你对你所开发的程序有了全面的了解.当然这对一个优秀的程序员来说未必是一个通用的标准,但很少人会反对我关于”理解GC是作为优秀Java程序员的必备技能”的

Java 垃圾回收(GC) 泛读

Java 垃圾回收(GC) 泛读 文章地址:https://segmentfault.com/a/1190000008922319 0. 序言 带着问题去看待 垃圾回收(GC) 会比较好,一般来说主要的疑惑在于这么几点: 为什么需要 GC ? 虚拟机(JVM) 与 垃圾回收(GC) 的关系? GC 的原理有哪些? 哪些 对象容易被 GC ? 等等 带着这些问题往下看: 1. 为什么需要 GC ? GC: 是Garbage Collection 的英文缩略,垃圾收集的意思. 为什么需要 GC?主要

【甘道夫】HBase随机宕机事件处理 &amp; JVM GC回顾

一.引言 本文记录了困扰团队两周的HBase随机宕机事件的解决方案,并回顾了JVM GC调优基础知识,供各位参考. 欢迎转载,请注明出处: http://blog.csdn.net/u010967382/article/details/42394031 二.实验环境 16台虚拟机,每台4G内存,1核CPU,400G硬盘 Ubuntu 14.04 LTS (GNU/Linux 3.13.0-29-generic x86_64) CDH5.2.0套装(包括相应版本的Hadoop,HIVE,Hbase

Understanding .net CLR garbage collection--(踏踏实实学好.Net系列)

引言 内存管理是计算机科学中一个相当复杂而有趣的领域.在计算机诞生的这几十年间,内存的管理的技术不断进步,使系统能够更加有效地利用内存这一计算机必不可少的资源. 一般而言,内存管理可以分为三类:硬件管理(如TLB),操作系统管理(如Buddy System,Paging,Segmentation),应用程序管理(如C++,Java,.net的内存管理机制).鉴于篇幅和笔者水平的限制,本文只涉及了内存管理的很小一部分,即.net中的内存管理方法..net是一个当代的应用程序框架,采用了内存自动管理