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

众所周知,.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); } } 

在这个小例子中,我们创建一个对象并在对象上设置一个简单变量。 点’***’对问题来说很重要:如果’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不移动这些对象,那么定义是否移动对象的“规则”是什么?

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

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

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

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