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

atl*_*ste 9 .net c# garbage-collection runtime

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

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

我最感兴趣的是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个小例子中,我们创建一个对象并在对象上设置一个简单变量.点'***'对问题来说很重要:如果'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 
Run Code Online (Sandbox Code Playgroud)

我或多或少期望在这里看到一个间接指针,可以更新 - 但显然GC比这更聪明.

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

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

xan*_*tos 5

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

"工作站"GC可以是并发的(因此部分不是停止世界),但请注意https://msdn.microsoft.com/library/ee851764.aspx.

当您使用带有并发垃圾回收的工作站垃圾回收时,回收的对象不会被压缩,因此堆大小可以相同或更大(碎片可以使其看起来更大).

请注意,对于所有GC,gen0和gen1始终是停止世界的.所以他们可以毫无问题地移动内存块.只有gen2可以在后台通过某些配置来完成(这个链接,信息在页面周围有点碎片),所以总是有一个"世界已经停止"的时刻,那里的内存已经存在释放可以压缩.

  • 我相信JIT实际上为GC输出了一些关于哪些机器代码范围使用哪些变量的元数据,因此GC不必查看指令指针以外的任何寄存器(它不知道给定寄存器中的值是否为无论如何都是一个参考). (2认同)