分析 GC Roots
对象的保留路径总是以 GC 根开始。 从垃圾回收器的角度来看,根是对一个对象的引用,该对象不能也不会被回收。 这使得根成为构建保留图的唯一可能起点。 在“谁保留了对象?”分析中,理解根类型可能至关重要。 有时检查保留路径并不能回答为什么对象仍然在内存中。 在这种情况下,查看 GC 根是有意义的。 例如,一个 RefCounted handle 表明某些非托管 COM 库保留了该对象。
.NET 中有四种可能的根类型:
堆栈引用 :对局部对象的引用。 这种根在方法执行期间存在。
静态引用 :对静态对象的引用。 这些根在整个应用程序域的生命周期内存在。
句柄 :通常,这些是用于托管代码和非托管代码之间通信的引用。 这种根至少要存在到非托管代码需要“托管”对象为止。
终结器引用 :对等待终结的对象的引用。 这些根存在直到终结器运行。
要分析保留路径的根,请使用显示对象保留路径的视图: 类似保留、 关键保留路径 和 到根的最短路径。 请注意,dotMemory 区分的所有根类型都属于上述列表中提到的类别之一。
常规局部变量
这是在方法中声明的局部变量(堆栈上的变量)。 对该变量的引用在方法生命周期内成为根。 例如:

请注意,在发布版本中,根的生命周期可能更短——JIT 可以在变量不再需要后立即丢弃它。
静态引用
当 CLR 遇到静态对象(类成员、变量或事件)时,它会创建该对象的全局实例。 该对象可以在整个应用程序生命周期内访问,因此静态对象几乎不会被回收。 因此,对静态对象的引用是主要的根类型之一。
集合初始化后,CLR 将创建集合的静态实例。 对该实例的引用将在应用程序域的生命周期内存在。

当通过字段引用静态对象时,dotMemory 会向您显示字段的名称。
F-可达队列/终结队列
CLR 提供了一种释放非托管资源的有用机制:终结模式。 System.Object 类型声明了一个虚方法 Finalize (也称为终结器),该方法在对象的内存被回收之前由垃圾回收器调用。 通常,您会重写此方法以释放非托管资源。 任何具有终结器的对象都会被放入终结队列(在 dotMemory 中,这些对象具有 终止队列 根)。 当垃圾回收发生时,GC 会在终结队列中找到这样的对象,但不会直接运行其终结器。 相反,GC 会将对象放入 F-可达队列(在 dotMemory 中为 F-Reachable 队列 根),并在单独的终结线程中运行终结器。 (这样做是为了性能,因为终结器可能运行任意数量的代码。)在下一次 GC 中,F-可达队列中的对象会被垃圾回收。 上述模式存在缺陷,这就是为什么 dotMemory 提供了一个特殊的 可终结对象 检查。
请注意,由于内存分析的特性,dotMemory 在拍摄快照之前总是会运行一次完整的 GC。 这就是为什么您不会在通过 dotMemory 拍摄的快照中找到具有 终止队列 根的对象。 这种根类型仅可能出现在原始内存转储中。

Pinning handle
托管代码和非托管代码的交互是垃圾回收器的一个额外问题。 例如,您需要将一个对象从托管堆传递到外部 API 库。 由于小对象堆在回收期间会被压缩,对象可能会被移动。 如果非托管代码依赖于对象的确切位置,这将成为一个问题。 解决方案之一是将对象固定在堆中。 在这种情况下,GC 会获取该对象的 pinning handle,这意味着该对象不能被移动(固定对象)。 因此,如果您看到一个 Pinning handle 根,那么可能是某些非托管代码保留了该对象。 例如, App 对象始终具有固定引用。

还有一种情况,您可能会在快照中看到一个 Pinning handle。 有时,无法正确识别 静态引用 :而不是 静态引用 根,您可能会看到一个由 Pinning handle 根保留的对象数组 Object[]。 这是静态引用工作方式的真实表现。

dotMemory 允许您将快照中的所有固定对象作为单独的对象集打开。 为此,请打开 检查 视图,并在 堆碎片 部分中点击 固定的对象 链接。

内部局部变量
由于托管对象在垃圾回收期间可能会被移动(请参阅 Pinning handle ),因此无法使用本机指针跟踪它们在堆中的位置。 在这种情况下,可以使用 内部指针。 内部指针声明了一个指向引用类型内部的指针,而不是指向对象本身。 如果您看到一个 内部局部变量 根持有一个对象,那么可能有一个内部指针指向该对象的内部。 要获取示例,请参阅 Microsoft Learn。

RefCounted handle
如果对象的引用计数达到某个值,根会阻止垃圾回收。 如果一个对象通过 COM Interop 传递到 COM 库,CLR 会为该对象创建一个 RefCounted handle。 这种根是必要的,因为 COM 无法执行垃圾回收。 相反,它使用引用计数。 如果对象不再需要,COM 会将计数设置为 0。 这意味着 RefCounted handle 不再是根,对象可以被回收。
因此,如果您看到 RefCounted handle ,那么可能该对象作为参数传递给了非托管代码。

Weak handle
与其他根不同,Weak handle 不会阻止被引用的对象被垃圾回收。 因此,对象可以随时被回收,但应用程序仍然可以访问它们。 对这些对象的访问是通过 WeakReference 类型的中间对象进行的。 这种方法在处理临时数据结构(如缓存)时可能非常高效。 由于弱引用无法在完整垃圾回收中存活,弱引用句柄只能与其他句柄结合使用。 例如, 弱引用,引用计数句柄。

常规句柄
当句柄类型未定义时,dotMemory 将其标记为 常规句柄。 通常,这些是整个应用程序生命周期内所需的系统对象的引用。 例如, OutOfMemoryException 对象。 为了防止其被回收,环境通过常规句柄引用该对象。
