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. Ubuntu搜狗输入法的安装

    Ubuntu搜狗输入法的安装 这个直接安装就可以了:因为现在的Ubuntu是16.04版本,输入法已经是Fcitx版本: 下载搜狗输入法For Linux之后,直接双击就可以安装了: 安装之后,需要注 ...

  2. CLion注冊码算法逆向分析实录(纯研究)

    声明 CLion程序版权为jetBrains全部.注冊码授权为jetBrains及其付费用户全部,本篇仅仅从兴趣出发,研究其注冊码生成算法. 不会释出不论什么完整的源码. 网上查了下.已有注冊机,所以 ...

  3. mysql-增删改数据

    一.增加操作 INSERT用来插入或添加行到数据库表的.插入用以下几种方式: 1.插入完整的行 insert into customers values(null,'Pep E,LaPew','100 ...

  4. NSURLCache、网络监測状态

    有时候.对同一个URL请求多次.返回的数据可能一样的: 比方server上的某张图片.不管下载多少次,返回的数据都是一样的.可是这些情况会造成下面问题: 1,用户流量的浪费. 2.程序响应速度不够快 ...

  5. jquery去重

    <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title> ...

  6. python 同步IO

    IO在计算机中指Input/Output 由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘.网络等,就需要IO接口.IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一 ...

  7. [luogu P3360] 偷天换日 解题报告(树形DP)

    题目链接:https://www.luogu.org/problemnew/show/P3360 题解: 首先我们把边上的消耗放到向下的点上,如果是叶子节点的话就先做一次0/1背包 发现这是一颗二叉树 ...

  8. Pickling

    Pickle translates almost any type of object into a string. pickle.dumps takes an object as a paramet ...

  9. java9新特性-2-安装与官网说明

    1.jdk 9的下载 http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html         下载安 ...

  10. Java类和对象9

    (1)创建一个叫做机动车的类:属性:车牌号(String),车速(int),载重量(double)功能:加速(车速自增).减速(车速自减).修改车牌号,查询车的载重量.编写两个构造方法:一个没有形参, ...