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

标签 c# .net garbage-collection clr pass-by-reference

据我所知,在 C# 中,refout 参数是通过仅传递相关值的原始地址来传递的。该地址可以是指向数组中元素或对象中字段的内部指针。

如果发生垃圾回收,可能对某个对象的引用是通过这些内部指针之一进行的,如:

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/47419240/

相关文章:

c# - 泛型参数的可空返回值

c# - 内部并在私有(private) api 中受到保护

c# - 在容器 dotnet 核心容器中运行时无法加载文件或程序集 'Newtonsoft.Json,版本 = 12.0.0.0

Java 垃圾回收和大对象

c# - 如何使用 Linq2SQL 在 ASP.NET 和 C# 中查询未知数量的 where 参数?

c# - 发送内联 MHTML

.net - 如何检查我的对象是否已正确处置?

c# - int rosu=Color.red.getRGB() 从 Java 到 C#

javascript - 为什么这段代码正在消耗内存并且没有被完全回收?

c# - Java 与 .Net 中的对象生命周期