C#垃圾收集器如何找到唯一引用为内部指针的对象?

mun*_*ent 14 .net c# clr garbage-collection pass-by-reference

在C#中,ref和outPARAMS是,据我所知,是,只透过相关值的原始地址通过.该地址可以是指向数组中的元素或对象内的字段的内部指针.

如果发生垃圾收集,则对某个对象的唯一引用可能是通过其中一个内部指针,如:

using System;

public class Foo
{
    public int field;

    public static void Increment(ref int x) {
        System.GC.Collect();
        x = x + 1;
        Console.WriteLine(x);
    }

    public static void Main()
    {
        Increment(ref new Foo().field);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,GC需要找到对象的开头并将整个对象视为可达.它是如何做到的?是否必须扫描整个堆以查找包含该指针的对象?这似乎很慢.

tum*_*tum 6

垃圾收集器将有一种快速的方法可以从托管内部指针中找到对象的开头。从那里它可以在执行扫描阶段时明显地将对象标记为“引用”。

没有 Microsoft 收集器的代码,但他们会使用类似于 Go 的 span 表的东西,它可以快速查找不同的内存“跨度”,您可以根据指针的大小键入最重要的 X 位,具体取决于您的大小选择要成为的跨度。从那里,他们利用每个跨度包含 X 个相同大小的对象这一事实来非常快速地找到您拥有的对象的标题。这几乎是一个 O(1) 操作。显然,Microsoft 堆会有所不同,因为它是按顺序分配的,而不考虑对象大小,但它们将具有某种 O(1) 查找结构。

https://github.com/puppeh/gcc-6502/blob/master/libgo/runtime/mgc0.c

// Otherwise consult span table to find beginning.
// (Manually inlined copy of MHeap_LookupMaybe.)
k = (uintptr)obj>>PageShift;
x = k;
x -= (uintptr)runtime_mheap.arena_start>>PageShift;
s = runtime_mheap.spans[x];
if(s == nil || k < s->start || (const byte*)obj >= s->limit || s->state != MSpanInUse)
    return false;
p = (byte*)((uintptr)s->start<<PageShift);
if(s->sizeclass == 0) {
    obj = p;
} else {
    uintptr size = s->elemsize;
    int32 i = ((const byte*)obj - p)/size;
    obj = p+i*size;
}
Run Code Online (Sandbox Code Playgroud)

请注意,.NET 垃圾收集器是一个复制收集器,因此只要在垃圾收集周期中移动对象,就需要更新托管/内部指针。GC 将根据 JIT 时已知的方法参数了解每个堆栈帧在堆栈内部指针的位置。

  • 是的。那讲得通。四处挖掘,我猜 dotnetcore 中的相应代码是 https://github.com/dotnet/coreclr/blob/master/src/gc/gc.cpp 中第 17143 行的 gc_heap::find_object()。谢谢! (2认同)