我正在尝试实现一种小型脚本语言,并打算确定哪种垃圾回收算法符合我的利益。我选择了Mark&Sweep,但我认为我误解了这个概念。
假设调用了一个任意函数,它创建了以下变量(可能它们没有名称,但为简单起见,假定它们是在此函数中创建的)。
f() {
/*f creates following variables*/
x = (1,2,(3,4,(5,6))); //this is tuple
a = x;
y = x[2];
z = y[2];
p = (10,y);
}
在上面的示例中,所有内容都是一个对象(整数,字符串,元组,双精度型等),并且元组保留指向其对象的指针。而且,每个对象都生活在堆中。当函数超出范围时,它必须删除分配的变量。功能范围如下所示。
+-----+
| |
| x +---------->(1,2,+)
+-----+ ^ |
| v
+-----+ | (3,4,+)
| | | ^ |
| a +-----------+ | v
+-----+ | (5,6)
| ^
+-----+ | |
| | | |
| y +----------------+ |
+-----+ | |
| |
+-----+ | |
| | | |
| z +---------------------+
+-----+ |
|
+-----+ |
| | |
| p +----------->(10,+)
+-----+
所有变量(a,x,y,z,p)都必须删除,但问题是如何?我知道Mark&Sweep是垃圾收集算法,我认为这个变量现在是我的垃圾。函数已完成其工作,并且必须将分配的内存返回给系统。
我尝试了以下操作,每个对象都持有一个标记位,并且在创建标记位设置为0之后。当程序将一个变量所持有的对象推入时,它将其标记转换为1,并且不会发生自由错误,因为程序知道它有一个所有者。到目前为止,这种方法一直有效。但是,如果我有很多如示例中所示的变量,该如何删除多个指针?
在这里,我的假设是,首先打破x及其对象之间的所有权。然后说出每个变量以标记其对象(如果该对象是一个元组,则将其对象标记位递归设置为1)。现在(1,2,...)对象的标记位由变量'a'设置为1;我可以尝试释放它,但程序不允许。如果我对表中的每个变量都进行此设置,那么复杂性看起来就很大(我对每个对象都有标记和清除阶段)。
我的问题是我对“标记和扫频”算法是否正确?根是我的变量吗?如何删除多个指针甚至循环引用?
答案 0 :(得分:1)
对于“标记并扫描”,您需要能够在一侧扫描所有分配的对象,在另一侧扫描所有可到达的对象。
假定所有已分配的对象上的标记位均已清除,请扫描可从根变量访问的所有对象。找到对象后,如果已将其标记,则将其跳过,否则对其进行标记并递归枚举其指向的对象。此阶段很棘手,因为此递归可能会变得太深,因此需要比纯递归更聪明的方法。
一旦所有可访问对象都被标记,请扫描所有已分配的对象:对于每个对象,如果已标记,请清除标记,否则它是不可访问的,因此请收集它(即:使其可重新分配或免费使用) )。
在Sweep阶段结束时,所有分配的对象都未标记,因此假设成立。一种效率稍高的实现方式将在两种状态之间交替,从而无需清除可到达对象上的标记位,从而减少了扫描阶段所需的内存带宽。
在程序的正常运行过程中更改对象引用时,不需要执行任何特殊操作:只需存储新引用的地址即可。
要有效地删除从全局变量引用的对象,您只需使这些变量指向null
或其他对象即可。
Mark&Sweep的优点是该算法相对简单,并且能够按周期收集复杂结构。缺点是 stop the world 模式下花费的时间,尤其是在多线程应用程序和实时应用程序甚至用户交互应用程序中。已经找到了解决这些问题的更高级的方法,但是它们的实现可能非常棘手。