C# 垃圾收集器在堆中移动数据时,引用是否得到更新?

C# 垃圾收集器在堆中移动数据时,引用是否得到更新?,c#,.net,garbage-collection,heap,C#,.net,Garbage Collection,Heap,我读到GC(垃圾收集器)在堆中移动数据是出于性能原因,我不太明白为什么,因为它是随机访问内存,可能是为了更好的顺序访问,但我想知道当堆中发生这样的移动时,堆栈中的引用是否会被更新。但可能偏移地址保持不变,但数据的其他部分被垃圾收集器移动,我不确定 我认为这个问题与实现细节有关,因为并不是所有垃圾收集器都可以执行这样的优化,或者它们可以执行,但不更新引用(如果这是垃圾收集器实现中的常见做法的话)。但我想得到一些特定于CLR(公共语言运行时)垃圾收集器的总体答案 我还在读Eric Lippert的“

我读到GC(垃圾收集器)在堆中移动数据是出于性能原因,我不太明白为什么,因为它是随机访问内存,可能是为了更好的顺序访问,但我想知道当堆中发生这样的移动时,堆栈中的引用是否会被更新。但可能偏移地址保持不变,但数据的其他部分被垃圾收集器移动,我不确定

我认为这个问题与实现细节有关,因为并不是所有垃圾收集器都可以执行这样的优化,或者它们可以执行,但不更新引用(如果这是垃圾收集器实现中的常见做法的话)。但我想得到一些特定于CLR(公共语言运行时)垃圾收集器的总体答案

我还在读Eric Lippert的“参考文献不是地址”一文,下面的一段让我有点困惑:

如果您认为引用实际上是一个不透明的GC句柄,那么 很明显,要找到与句柄关联的地址 你必须以某种方式“修复”对象。你必须告诉GC“直到 请进一步注意,不能将具有此控制柄的对象移入 内存,因为某人可能有指向它的内部指针”。(那里 有各种各样的方法可以做到这一点,这超出了本文的范围 熨平板。)

听起来好像对于引用类型,我们不希望数据被移动。那么,我们在堆中还存储了什么,可以在堆中四处移动以优化性能?可能是我们存储在那里的类型信息?顺便说一句,如果你想知道那篇文章是关于什么的,那么Eric Lippert正在比较引用和指针,并试图解释为什么说引用只是地址可能是错误的,即使它是C#如何实现的

另外,如果我上面的任何假设是错误的,请纠正我。

看看,有一节是关于内部指针和固定指针的:

C++/CLI提供了两种解决此问题的指针。 第一类称为内部指针,由 运行时以反映指向的对象的新位置 每次重新定位对象时。所指向的物理地址 内部指针从未保持不变,但始终指向 同一个物体。另一种称为固定指针,它 防止GC重新定位对象;换句话说,它是固定的 将对象复制到CLR堆中的特定物理位置。有一些 限制,内部、固定和固定之间可以进行转换 本机指针

由此可以得出结论,引用类型确实在堆中移动,它们的地址也确实发生了变化。在标记和扫描阶段之后,对象在堆内被压缩,从而实际移动到新地址。CLR负责跟踪实际存储位置,并使用内部表更新这些内部指针,确保在被访问时仍指向对象的有效位置

这里有一个例子:

ref struct CData
{
    int age;
};

int main()
{
    for(int i=0; i<100000; i++) // ((1))
        gcnew CData();

    CData^ d = gcnew CData();
    d->age = 100;

    interior_ptr<int> pint = &d->age; // ((2))

    printf("%p %d\r\n",pint,*pint);

    for(int i=0; i<100000; i++) // ((3))
        gcnew CData();

    printf("%p %d\r\n",pint,*pint); // ((4))
    return 0;
}

是的,引用在垃圾收集期间得到更新。因此,当堆被压缩时,对象必然会被移动。压实有两个主要目的:

  • 通过更高效地使用处理器的数据缓存,它使程序更高效。这在现代处理器上是一个非常非常重要的问题,与执行引擎相比,RAM的速度非常慢,只有两个数量级。当处理器必须等待RAM提供可变值时,它可能会暂停数百条指令
  • 它解决了堆所面临的碎片问题。释放被活动对象包围的小对象时会发生碎片。除了大小相等或更小的物体外,不能用于其他任何东西的孔。不利于内存使用效率和处理器效率。请注意,LOH是.NET中的大型对象堆,它没有被压缩,因此会遇到碎片问题。关于这一点,现在有很多问题
尽管Eric的说教,对象引用实际上只是一个地址。一个指针,与C或C++程序中使用的完全一样。效率很高,这是必然的。在移动一个对象之后,GC所要做的就是将指针中存储的地址更新到移动的对象。CLR还允许为对象分配句柄、额外引用。在.NET中作为GCHandle类型公开,但仅在GC需要帮助确定对象是否应保持活动状态或不应移动时才需要。仅在与非托管代码互操作时才相关

不那么简单的是找到那个指针。CLR投入了大量资金,以确保能够可靠、高效地完成这项工作。这样的指针可以存储在许多不同的地方。更容易找到的是存储在对象字段、静态变量或GCHandle中的对象引用。硬指针是存储在处理器堆栈或CPU寄存器上的指针。例如,对于方法参数和局部变量发生


CLR需要提供的一个保证是,GC始终能够可靠地遍历线程堆栈。因此,它可以找到存储在堆栈框架中的局部变量。然后,它需要知道在这样的堆栈框架中查找哪里,这是JIT编译器的工作。当它编译一个方法时,它不仅生成该方法的机器代码,还构建一个表来描述这些指针的存储位置。如果我没记错的话,您可以在中找到更多详细信息。

是的。GC中有一个“重新定位”阶段,移动所有对象以删除/减少内存碎片,在这个阶段,对移动对象的引用得到更新。我将尝试从Channel9(或MSDN文章)找到链接,并更新此评论。编辑:这是链接:(有一个l
012CB4C8 100
012A13D0 100