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

相关文章:

.net - C#NetCDF库

Android 内存泄漏和垃圾收集

java - 关闭 SQL 连接 Java Webservice

haskell - 如何减少 Haskell 应用程序中的内存使用量?

c# - 跳过 Aero 窗口动画

c# - 嵌入资源大小

.net - 如何在 XML 属性中编码 JavaScript 文本?

c# - WPF 非阻塞、自动关闭的消息框

c# - Elasticsearch MoreLikeThis 查询从不返回结果

c# - 通用类型的 Unity 配置