压缩发生后GC如何更新引用

.NET垃圾收集器收集对象(回收它们的内存),并执行内存压缩(将内存碎片降到最低)。

我想知道,由于应用程序可能有很多对象的引用,当GC(或CLR)由于GC进行压缩而导致对象的地址更改时,GC(或CLR)如何管理这些对象引用。


这个概念很简单,垃圾收集器只是更新任何对象引用,并将它们重新指向移动的对象。

实现有点棘手,原生代码和托管代码之间没有真正的区别,它们都是机器代码。 对象引用没有什么特别之处,它只是运行时的一个指针。 所需要的是收集器可靠地找回这些指针,并将它们识别为引用被管理对象的类型。 不仅仅是在指向对象在压缩时移动时更新它们,还要识别确保对象不会过早收集的实时引用。

这对存储在GC堆中的类对象中存储的任何对象引用很简单,CLR知道对象的布局以及哪些字段存储指针。 存储在堆栈或cpu寄存器中的对象引用并不那么简单。 像局部变量和方法参数一样。

执行托管代码使其与本地代码截然不同的关键属性是,CLR可以可靠地迭代由托管代码拥有的堆栈帧。 通过限制用于设置堆栈帧的代码的种类来完成。 这在本地代码中通常是不可能的,“帧指针省略”优化选项特别讨厌。

首先堆栈框架可以让它找到存储在堆栈上的对象引用。 并让它知道该线程当前正在执行托管代码,以便还应该检查cpu寄存器的引用。 从托管代码转换到本地代码需要在收集器识别的堆栈上写入一个特殊的“cookie”。 所以它知道任何后续的堆栈帧都不应该被检查,因为它们将包含任何时候都不会引用被管理对象的随机指针值。

在启用非托管代码调试时,您可以在调试器中看到它。 查看Call Stack窗口并记下[Native to Managed Transition]和[Managed to Native Transition]注释。 这是调试器识别这些cookie。 它也很重要,因为它需要知道本地窗口是否可以显示任何有意义的内容。 栈走也暴露在框架中,请注意StackTrace和StackFrame类。 对于沙盒来说,代码访问安全性(CAS)执行堆栈散步非常重要。


为了简单起见,我将假定一个世界停止的GC,其中没有任何对象被固定,每个对象都在每个GC周期被扫描和重定位,并且没有任何目标与任何来源重叠。 实际上,.NET GC有点复杂,但是这应该给出一个很好的工作方式。

每次检查参考时,都有三种可能性:

  • 它是空的。 在这种情况下,不需要采取任何行动。

  • 它标识一个头部表示它不是重定位标记(下面描述的特殊对象)的对象。 在这种情况下,将对象移动到新位置,并用包含新位置的三字重定位标记替换原始对象,包含对当前对象的刚才观察到的引用的对象的旧位置以及内部偏移那个对象。 然后开始扫描新的对象(系统可以忘记当前正在扫描的对象,因为它只记录了它的地址)。

  • 它标识一个头部表示它是重定位标记的对象。 在这种情况下,更新正在扫描的参考以反映新地址。

  • 系统完成扫描当前对象后,它可以查看其旧位置,以便在开始扫描当前对象之前了解它正在执行的操作。

    一旦物体被重新定位,其前三个单词的前一个内容将在其新位置处可用,并且将不再需要旧物体。 由于对象的偏移量始终为4的倍数,并且单个对象的每个对象都被限制为2GB,因此只需要所有可能的32位值中的一小部分来保存所有可能的偏移量。 条件是在对象的头标,至少一个字具有它永远保持比对象重定位标记的任何其他至少2 ^ 29的值,并提供每个对象分配至少12个字节,这是可能的对象扫描处理任何而不需要在不再需要内容的旧副本占用的空间之外的任何依赖于深度的存储。


    垃圾收集

    每个应用程序都有一组根。 根标识存储位置,这些存储位置引用托管堆上的对象或设置为空的对象。 例如,应用程序中的所有全局和静态对象指针都被认为是应用程序根的一部分。 另外,线程堆栈上的任何局部变量/参数对象指针都被视为应用程序根的一部分。 最后,包含指向托管堆中对象指针的任何CPU寄存器也被视为应用程序根部分的一部分。 活动根目录由实时(JIT)编译器和公共语言运行库维护,并可供垃圾收集器的算法访问。

    当垃圾收集器开始运行时,它会假定堆中的所有对象都是垃圾。 换句话说,它假定应用程序的根没有引用堆中的任何对象。 现在,垃圾收集器开始漫步根,并构建从根到达的所有对象的图形。 例如,垃圾收集器可能会找到一个指向堆中对象的全局变量。

    一旦这部分图形完成,垃圾收集器将检查下一个根,然后再次移动对象。 当垃圾收集器从一个对象走向另一个对象时,如果它试图将一个对象添加到它之前添加的图形中,那么垃圾收集器可以停止沿着该路径行进。 这有两个目的。 首先,它有助于提高性能,因为它不会多次遍历一组对象。 其次,如果你有任何循环链接的对象列表,它可以防止无限循环。

    一旦所有的根都被检查完毕,垃圾收集器的图形就包含了从应用程序的根部以某种方式可以到达的所有对象的集合; 任何不在图中的对象都不能被应用程序访问,因此被视为垃圾。 垃圾收集器现在线性遍历堆,查找连续的垃圾对象块(现在称为可用空间)。 垃圾回收器然后将非垃圾对象在内存中向下移动(使用多年来已知的标准memcpy函数),从而消除堆中的所有空隙。 当然,移动内存中的对象会使所有指向对象的指针失效。 所以垃圾收集器必须修改应用程序的根,以便指针指向对象的新位置。 另外,如果任何对象包含指向另一个对象的指针,那么垃圾收集器也负责更正这些指针。

    C#固定语句

    fixed语句设置一个指向受管变量的指针,并在语句执行过程中“引用”该变量。 如果没有固定的话,指向可移动管理变量的指针就没什么用处,因为垃圾回收可能会无法预测地重定位变量。 C#编译器只允许您在固定语句中指定托管变量的指针。

    垃圾收集:Microsoft .NET Framework中的自动内存管理

    固定语句(C#参考)

    链接地址: http://www.djcxy.com/p/79099.html

    上一篇: How does the GC update references after compaction occurs

    下一篇: How to store references in a mark and sweep garbage collector?