.NET 运行时如何移动内存?

How does the .NET runtime move memory?

众所周知,.NET 垃圾收集器不仅 'delete' 堆上的对象,而且还使用内存压缩来对抗内存碎片。据我了解,基本上内存被复制到一个新的地方,而旧的地方在某个时候被删除了。

我的问题是:这是如何工作的?

我最好奇的是 GC 在单独的线程中运行,这意味着我们正在处理的对象可以在我们执行时由 GC 移动 我们的代码

问题的技术细节

为了说明,让我更详细地解释一下我的问题:

class Program
{
    private int foo;
    public static void Main(string[] args)
    {
        var tmp = new Program(); // make an object
        if (args.Length == 2)    // depend the outcome on a runtime check
        {
            tmp.foo = 12;        // set value ***
        }
        Console.WriteLine(tmp.foo);
    }
}

在这个小例子中,我们创建了一个对象并在对象上设置了一个简单的变量。点“***”是问题的全部内容:如果 'tmp' 的地址移动,'foo' 将引用不正确的内容,一切都会崩溃。

垃圾收集器在单独的线程中运行。据我所知,'tmp' 可以在此指令中移动,而 'foo' 可能会以不正确的值结束。但不知何故,奇迹发生了,但它没有。

至于反汇编程序,我注意到编译后的程序确实采用了 'foo' 的地址并移入值 '12:

000000ae 48 8B 85 10 01 00 00 mov         rax,qword ptr [rbp+00000110h] 
000000b5 C7 40 08 0C 00 00 00 mov         dword ptr [rax+8],0Ch 

我或多或少希望在这里看到一个间接指针,它可以被更新——但显然 GC 比它更聪明。

此外,我没有看到任何检查对象是否已移动的线程同步。那么GC是如何在执行线程中更新状态的呢?

那么,这是如何工作的?如果GC不移动这些对象,那么定义是否移动对象的'rule'是什么?

.NET GC 是(至少部分地)"stop-the-world" GC:它在执行其工作之前停止托管线程,执行其工作,然后重新启动托管线程。

"Workstation" GC 可以是并发(所以部分不是停止世界)但请注意https://msdn.microsoft.com/library/ee851764.aspx

When you are using workstation garbage collection with concurrent garbage collection, the reclaimed objects are not compacted, so the heap size can be the same or larger (fragmentation can make it appear to be larger).

请注意,对于所有 GC,gen0 和 gen1 总是停止世界。所以他们可以毫无问题地移动内存块。只有 gen2 可以由一些具有某些配置的 GC 在后台完成(这个 link,页面周围的信息有点零散),所以总是有一个 "the-world-is-stopped" 的时刻,内存已经被释放可以压缩。