【问题标题】:Multiple pointers and Mark & Sweep多指针和Mark & Sweep
【发布时间】:2019-07-31 04:44:55
【问题描述】:

我正在尝试实现一种小型脚本语言,并打算决定哪种垃圾收集算法适合我的优势。我选择了 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;我可以尝试释放它,但程序不允许。如果我为表中的每个变量都做这个,复杂性看起来很大(我对每个对象都有标记和扫描阶段)。

我的问题是我对 Mark & Sweep 算法是否正确?根是我的变量吗?如何删除多个指针甚至循环引用?

【问题讨论】:

  • Mark & Sweep 由两个阶段组成,就像它的名字一样。您需要实现 Mark 阶段 - 从根(您的变量)开始遍历所有可从它们访问的对象(您的元组和子元组)。以某种方式标记(一点就足够了)那些访问过的人。那些没有被标记的都是垃圾要被删除——在这种情况下通过清扫。
  • 那么我对根源是正确的。但是,我需要一次又一次地扫吗?例如,变量 0(在示例中其名称为“x”)我打破了所有权并进行了扫描,之后再次对变量 1 进行相同的处理。在打破所有变量的所有权之前,我将进行如此多的扫描。我看不到这一点,因为看起来我要停下来收集垃圾。 @KonradKokosa
  • @fbgencer 每当垃圾收集器运行时,您都会进行标记和清除。什么时候或多或少取决于你。通常,只要内存消耗超过某个阈值,分配的内存就会调用 GC。许多系统还允许程序员手动调用 GC(尽管该功能不经常使用)。

标签: c garbage-collection programming-languages


【解决方案1】:

对于 Mark and Sweep,您需要能够扫描一侧的所有已分配对象和另一侧的所有可访问对象。

  • 假设所有已分配对象上的标记位均已清除,则扫描所有可从根变量访问的对象。当找到一个对象时,如果它已经被标记,则跳过它,否则标记它并递归枚举它指向的对象。这个阶段很棘手,因为这种递归可能太深了,所以需要比普通递归更聪明的方法。

  • 一旦所有可达对象都被标记,扫描所有分配的对象:对于每个对象,如果它被标记,则清除标记,否则它不可达,所以收集它(即:使其可用于重新分配或释放它)。

在 Sweep 阶段结束时,所有分配的对象都没有标记,因此假设成立。更高效的实现方式是在两种状态之间交替,避免清除可达对象上的标记位,从而减少扫描阶段所需的内存带宽。

当您在程序的正常过程中更改对象引用时,您不需要做任何特别的事情:只需存储新引用的地址即可。

为了有效地删除从你的全局变量中引用的对象,你应该让这些变量指向null或其他一些对象。

Mark & Sweep 的优点是算法相对简单,并且能够收集具有循环的复杂结构。缺点是 stop the world 模式所花费的时间,尤其是在多线程应用程序和实时应用程序甚至用户交互应用程序中。已经找到了更高级的方法来处理这些问题,但它们的实现可能非常棘手。

【讨论】:

  • 您能描述一下那些高级方法吗?或者名字就足够了。
  • @fbgencer:除了引用计数,这是跟踪算法的常见替代方法,维基百科文章en.wikipedia.org/wiki/Garbage_collection_(computer_science) 提到了 JVM 实现中使用的高级算法的不同名称:G1、Parallel、ConcMarkSweep (CMS)、 Serial、Shenandoah... 还有Generational Garbage Collection 方法,通过根据对象的年龄对对象进行分组来提高缓存效率。一篇有趣的文章解释了 Go 语言 GC 的基本原理和实现:blog.golang.org/ismmkeynote
  • @fbgencer:引用计数可用于实时系统,因为它以恒定的时间运行,具有合理的时间和空间开销。主要缺点是无法检测到无法到达的自引用结构。根据应用程序使用的特定内存,这可能不是问题,并且存在新技术来检测此类无法访问的对象并在特定的标记和清除阶段收集它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-24
  • 2017-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多