据我所知,在 C# 中,ref
和 out
参数是通过仅传递相关值的原始地址来传递的。该地址可以是指向数组中元素或对象中字段的内部指针。
如果发生垃圾回收,可能仅对某个对象的引用是通过这些内部指针之一进行的,如:
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);
}
}
在那种情况下,GC 需要找到对象的开头并将整个对象视为可访问的。它是如何做到的?它是否必须扫描整个堆以查找包含该指针的对象?这看起来很慢。
最佳答案
垃圾收集器将有一种快速的方法从托管内部指针找到对象的开始。从那里它可以在执行扫描阶段时将对象明显标记为“已引用”。
没有 Microsoft 收集器的代码,但他们会使用类似于 Go 的跨度表的东西,它可以快速查找不同的内存“跨度”,你可以根据指针的最高有效 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;
}
请注意,.NET 垃圾收集器是一个复制收集器,因此无论何时在垃圾收集周期中移动对象,都需要更新托管/内部指针。 GC 将根据 JIT 时已知的方法参数知道每个堆栈帧的堆栈内部指针在堆栈中的位置。
关于c# - C# 垃圾收集器如何找到唯一引用是内部指针的对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48287071/