GC复制算法

Copying GC,Marvin L.Minsky,1963

该算法吧某个空间里的活动对象复制到其他空间,把原空间里的所有对象进行回收。再此我们把复制活动对象的空间称为From空间,将粘贴活动对象的空间称为To空间


先介绍Robert R.Fenichel与Jerome C.Yochelson 研究出的GC复制算法

GC复制算法是利用From空间进行分配的。当From空间被完全占满时,GC会将活动对象全部复制到To空间。当复制完成后,该算法会把From空间和To空间互换GC也就结束了。From空间和To空间大小必须一致。这是为了保证能把From空间中的所有活动对象都收纳到To空间里。如下图所示:

下面是GC复制算法的copying()函数

copying(){
$free = $to_start
for(r :$roots)
*r = copy(*r)
swap($from_start, $to_start) }

$free 是指示分块开头的变量。先将$free设置To空间的开头然后从根开始引用复制对象。copy()函数将作为参数传递的对象*r复制的同时,也将其子对象进行递归复制。复制结束后返回指针,这里返回的指针指向的是*r所在的新空间对象

在GC结束时,原空间的对象会作为垃圾被回收。因此,由根指向原空间对象的指针也会被重写成指向返回值的新对象的指针。

copy()函数 将传递给自己的参数复制,然后递归复制其孩子

copy(obj){
if(obj.tag != COPIED)
copy_data($free, obj, obj.size)
obj.tag = COPIED
obj.forwarding = $free
$free += obj.size
for(child :children(obj.forwarding))
*child = copy(*child)
return obj.forwarding
}
  • 首先检测obj是否复制过?这里使用了obj.tag 是obj的一个域。如果是COPIED就是复制过。
  • 如果没有被复制,函数会复制obj,返回指向新空间对象的指针。如果已经复制则函数会返回新空间对象的地址。

使用copy_data()将函数复制到$free指示的空间

如图示,对象A复制生成了A'。新空间$free,大小时size,这时候新对象的指针还是指向C,D(from空间)

之后给该对象的tag改为COPIED,这样即使被多个对象引用也不会复制多次。

把指向新空间对象的指针放在obj.forwarding里。之后当找到指向原空间对象的指针时,需要把找到的指针换到新空间,forwarding 指针正是为此准备的。如图所示

  • 可以看到图中From空间A的A', 我们需要更换根指针时,只需要让根的指向,变为A'即可。
  • 可以看到A多个一个对号,这个对号就是标记COPIED.
  • 按照obj长度向前移动$free,递归调用copy()函数。

这样一来,To空间指向From空间的指针就全部指向To空间了。如下图示。

A、C、D 保持着原有的引用关系被从From空间复制到了To空间。从根指向A的指针也被换成了AꞋ。留在From空间里的A到D都被回收了。

要实现这个方法有两个条件,首先每个对象必须有2个域用作COPIED,forwarding。接下来COPYED标签为了挪动obj中的域,必须选1个mutator绝对不会用到的值。

new_obj()函数

new_obj(size){
if($free+size > $from_start + HEAP_SIZE/2) //判断是否有空间
copying() //没有的话进行GC复制压缩
if($free+size > $from_start + HEAP_SIZE/2) //在此判断空间
allocation_fail() //分配失败
obj = $free //分配成功
obj.size = size
$free += size
return obj }
  • HEAP_SIZE 表示的是把 From 空间和 To 空 间加起来的大小
  • 如果分块足够大,那么程序就会把size大小的空间从这个分块中分割出来交给mutator。别忘了还得把$free移动size个长度。

执行过程

假设堆里对象的配置如下图事先将$free指向To开头。

  • 首先直接被根引用的对象B和G,B先被复制到了To空间。如下图。

  • 我们将B被复制后的对象称为 BꞋ,对B标记上COPIED和forwarding。

  • 下面我们要把还在From空间里的A复制到To。如下图

  • 这才是完成了B的复制,因为A没有子对象所以对A的复制也就完成了。

  • 接下来就是要复制同样是被根引用的G了及其孩子对象。

  • B,E均是G的孩子,但是B已经被复制过,所以只要把从G指向B的指针换到BꞋ上就行了。

  • 最后只要把 From 空间和 To 空间互换,GC 就结束了。

  • 其中C,D,F没办法查找,所以会被回收掉。

  • 这里的搜索顺序是B,A,G,E。其实也可以分为广度优先和深度优先。

优点

  1. 优秀的吞吐量:

标记清除算法消耗吞吐量的是搜索活动对象(标记)和搜索整体堆(清除)。

GC复制算法只搜索并复制活动对象。能在较短时间内完成GC。堆越大相比之下性能约明显。但是还是和对象数量成正相关。

  1. 可实现高速分配:

GC复制算法不使用空闲链表,而是使用连续的内存空间。所以相比标记清除算法来说,GC复制算法会很快速的分配。因为标记清除最坏的情况是每次分配都在队尾进行处理。

  1. 不会发生碎片化:

活动对象被集中安排在 From 空间的开头对吧。像这样把对象重新集中,放在堆的一端的行为就叫作压缩。在GC复制算法中,每次运行GC时 都会执行压缩。 因为压缩的原因,所以不会产生碎片化。(当然标记清除算法也可以通过压缩来解决碎片化问题。)

  1. 与缓存兼容:

GC复制算法中有引用关系的对象离得比较近。这种情况下mutator的速度会非常快,通过压缩来吧有引用关系对象安排在堆中较劲的位置。(其实这些东西在设计项目的时候感觉都用得上。)

缺点

  1. 堆使用效率低下

GC复制将内存二等分,利用率最高是50%,最低的话可想而知。我们可以通过GC复制搭配标记清除来改善这一点。后期面会介绍的详细一点。

  1. 不兼容保守式GC算法

GC标记清除和保守式GC兼容,因为GC标记清除不能移动对象。而GC复制算法则是全部移动,重写指针。不过GC复制算法和保守式GC算法可以进行组合这一点在后面保守式GC会提到。

  1. 递归调用函数

复制某个对象时要递归复制它的子对象。因此在每次进行复制的时候都要调用函数,由此带来的额外负担不容忽视。大家都知道比起这种递归算法,迭代算法更能高速地执行,因为在每次递归调用时都会消耗栈,所以还有栈溢出的可能。将在后面Cheney的GC复制算法中提到,

在循环的次数较大的时候,迭代的效率明显高于递归(比如计算40个斐波那契数会是3-4倍的时间差距。)。迭代就是代码段的循环,例如计算斐波那契通过for循环来进行只不过是吧上次计算的值保存后再次使用递归是是要去建立新的副本的,会消耗大量的内存和时间,而迭代反复调用同一块函数的内存

Cheney的GC复制算法

C.J.Cheney, 1970

相比Fenichel和Yochelson的GC复制算法,Cheney使用迭代而不是递归

copying(){
scan = $free = $to_start
for(r :$roots)
*r = copy(*r)
while(scan != $free)
for(child :children(scan))
*child = copy(*child)
scan += scan.size
swap($from_start, $to_start)
}
  • 先将scan和$free两个指针初始化。scan是用于搜索复制完成的对象的指针。$free是指向分块开头的指针。
  • 首先复制的是直接从根引用的对象。然后搜索复制完成的对象,迭代的去复制其子对象
  • 最后把From和To互换。关键点仍是copy().

copy()函数

copy(obj){
if(is_pointer_to_heap(obj.forwarding, $to_start) == FALSE)
copy_data($free, obj, obj.size)
obj.forwarding = $free
$free += obj.size
return obj.forwarding }
  • 先检查是否复制完毕,对于is_pointer_to_heap(obj.forwarding, $to_start),如果obj.forwarding指向To,则返回True,否则返回FALISE.
  • 复制对象,并对forwarding指针进行设定。

Cheney的GC复制算法并没有使用到COPYIED标签而是使用forwarding指针。那么forwarding指针是如何判断是否复制完毕呢?在没有复制之前,指针指的的From区域,在复制完后指针指的的To区域,很明显我们可以通过forwarding指针来区分。

执行过程

引入了scan指针。下面是堆的初始状态。

  • 首先福之所有从根直接引用的对象,这里就是BG。

  • 这时候scan指着To的开头,$free向右移动B.size+G.size。关键是scan每次对复制完成的对象搜索时,以及$free每次对没复制的对象复制时,向右移动。至scan和$free一直。
  • 下面对 BꞋ 进行搜索。

  • 搜索 BꞋ ,然后把被 BꞋ引用的A复制到了To。同时把scan和$free右移动。
  • 下面是搜索GꞋ,之后E被复制到了To,从GꞋ指向B的指针换到了 BꞋ。
  • 之后就改搜索 AꞋ和 EꞋ了,不过他们没有子对象,所以不能进行复制。此时scan和$free一致所以最后交换空间GC就结束

我们看到了,scan和$free向右移动不是同步的,我们之前说过,单纯的吧B变为BꞋ是没有复制完成的,但是为了存储$free还要向后移位。此时没有复制完所以scan不搜索对象,所以不移位。

按B.G.A.E的顺序来搜索对象,采用的是广度优先

被隐藏的队列

广度优先需要FIFO结构队列支持,需要把搜索对象保存到队列中一遍搜索一边取出。但是我们要怎么进行呢?

实际上scan和$free之间的堆变成了队列。scan左边是已经搜索完毕的空间,$free每次右移,队列里就会增加对象。scan每次右移队列里就会取出对象。这样就能满足FIFO的条件。这样我们就不用去设置单独的队列实现功能了。

优缺点

优点:使用迭代算法,减少了函数的调用和额外的栈消耗。省去了用于搜索内存空间这一点

缺点:我们可以从图中看出,复制的对象之间有引用关系,但是他们相邻的程度随着对象的增多而减少。这就不利于缓存的使用了。因此说,它的缓存兼容性没有深度优先的GC复制算法好。只能说比起标记清除和引用计数来说好一些

Copying GC (Part one)的更多相关文章

  1. Sapphire: Copying GC Without Stopping the World

    https://people.cs.umass.edu/~moss/papers/jgrande-2001-sapphire.pdf Many concurrent garbage collectio ...

  2. Copying GC (Part two :Multi Space Copying GC)

    目录 近似深度优先搜索方法 Cheney的GC复制算法 前提 执行结果 多空间复制算法 multi_space_copying()函数 mark_or_copy() copy() 执行过程 优缺点 近 ...

  3. jvm垃圾收集器之Throughput GC

    呃.HotSpot VM的GC组老人之一Jon Masamitsu很久之前就写过blog讲解这个:https://blogs.oracle.com/jonthecollector/entry/our_ ...

  4. Conservative GC (Part two :MostlyCopyingGC )

    目录 MostlyCopyingGC 概要 堆结构 分配 new_obj()函数 add_pages()函数 GC执行过程 mostly_copying()函数 promote_page()函数 pa ...

  5. Investigating Your RAM Usage

    转载自:http://developer.android.com/intl/zh-cn/tools/debugging/debugging-memory.html Because Android is ...

  6. Android内存管理(5)*官方教程:Logcat内存日志各字段含义,查看当前内存快照,跟踪记录内存分配,用adb查看内存情况时各行列的含义,捕获内存快照的3种方法,如何让程序暴漏内存泄漏的方法

    Investigating Your RAM Usage In this document Interpreting Log Messages                 内存分析日志中各消息的含 ...

  7. 深入理解Android内存管理原理(六)

    一般来说,程序使用内存的方式遵循先向操作系统申请一块内存,使用内存,使用完毕之后释放内存归还给操作系统.然而在传统的C/C++等要求显式释放内存的编程语言中,记得在合适的时候释放内存是一个很有难度的工 ...

  8. Throughtput收集器

    介绍 JVM里面的Throughtput收集器是一款关注吞吐量的垃圾收集器.该收集器是唯一一个实现了UseAdaptiveSizePolicy策略的收集器,允许用户通过指定最大暂停时间和垃圾收集时间占 ...

  9. Go 2 Draft Designs

    Go 2 Draft Designs 28 August 2018 Yesterday, at our annual Go contributor summit, attendees got a sn ...

随机推荐

  1. POJ 2739 Sum of Consecutive Prime Numbers(素数)

    POJ 2739 Sum of Consecutive Prime Numbers(素数) http://poj.org/problem? id=2739 题意: 给你一个10000以内的自然数X.然 ...

  2. HDU 1022 Train Problem I 模拟栈题解

    火车进站,模拟一个栈的操作,额外的栈操作,查看能否依照规定顺序出栈. 数据量非常少,故此题目非常easyAC. 直接使用数组模拟就好. #include <stdio.h> const i ...

  3. Oracle单实例情况下的library cache pin的问题模拟与问题分析

    Oracle单实例情况下的library cache pin的问题模拟与问题分析 參考自: WAITEVENT: "library cache pin" Reference Not ...

  4. HDOJ 4975 A simple Gaussian elimination problem.

    和HDOJ4888是一样的问题,最大流推断多解 1.把ISAP卡的根本出不来结果,仅仅能把全为0或者全为满流的给特判掉...... 2.在残量网络中找大于2的圈要用一种类似tarjian的方法从汇点開 ...

  5. 人生之清单(list of life)

    人生要走走停停,想想记记,修修改改,再向前走.国家工程,公平正义,一腔热血,都很容易燃起来,热血起来. 1. 没得选,有得选,怎么办 没得选 原生家庭,自己怎么生,怎样的父母: 遇见谁,会有怎样的对白 ...

  6. BZOJ 2342 Manacher

    思路: 首先用manacher可以求出以i和i+1中间为对称轴,最长回文串能扩增的长度p[i]. 然后4*(y-x)能更新答案,当且仅当y≤x+p[x]/2且y-p[y]≤x. 按i-p[i]将所有点 ...

  7. vue的鼠标移入和移出

    vue的鼠标移入和移出 需求(鼠标到预约二维码显示,预约添加背景色) 实现 <!--html部分--> <ul class="person_list"> / ...

  8. 关于ios11和Chrome浏览器非HTTPS 对高德定位不支持问题

    最近,在开发二维码签到系统时,需要对用户的签到的经纬度进行获取,并且判断签到距离是否在可支持范围内. 使用了高德地图.因为本人最近刚刚入手了一款新的Iphone7里面是最新的ios11系统.发现我的手 ...

  9. ArchLinux dwm的安装和配置

    dwm官网:https://dwm.suckless.org/ dwm是一个简洁的平铺式窗口管理器 配置简单,使用便捷,没有多少依赖,占用内存非常小 总之dwm正合口味 安装方法 首先在官网下载dwm ...

  10. [NOIP2012提高组]国王游戏

    题目:洛谷P1080.Vijos P1779.codevs1198. 题目大意:国王和每个大臣左.右手各写了一个数.规定每个大臣得到的金币数为他前面所有人左手的数字的乘积除以他自己右手的数(向下取整) ...