【问题标题】:.Net Why GC does not remove objects from heap?.Net 为什么 GC 不从堆中删除对象?
【发布时间】:2018-11-09 16:16:09
【问题描述】:

我已经编写了简单的测试程序:

namespace GCTest {
class Program {
    static void Main(string[] args) {
        var a1 = new A();
        a1.AProperty = new A();
        a1.AProperty.AProperty = new A();
        a1.AProperty.AProperty.AProperty = new A();
        a1.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a1 created");
        Console.ReadKey();

        var a2 = new A();
        a2.AProperty = new A();
        a2.AProperty.AProperty = new A();
        a2.AProperty.AProperty.AProperty = new A();
        a2.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a2 created");
        Console.ReadKey();

        var a3 = new A();
        a3.AProperty = new A();
        a3.AProperty.AProperty = new A();
        a3.AProperty.AProperty.AProperty = new A();
        a3.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a3 created");
        Console.ReadKey();

        var a4 = new A();
        a4.AProperty = new A();
        a4.AProperty.AProperty = new A();
        a4.AProperty.AProperty.AProperty = new A();
        a4.AProperty.AProperty.AProperty.AProperty = new A();
        Console.WriteLine("a4 created");
        Console.ReadKey();

        var a5 = new A();
        a5.AProperty = new A();
        a5.AProperty.AProperty = new A();
        a5.AProperty.AProperty.AProperty = new A();
        a5.AProperty.AProperty.AProperty.AProperty = new A();

        Console.WriteLine("a5 created");
        int a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        GC.Collect();
        GC.WaitForPendingFinalizers();
        a1Gen = GC.GetGeneration(a1);
        Console.WriteLine("a1 generation: " + a1Gen);
        Console.ReadKey();

        a1 = a2 = a3 = a4 = a5 = null;
        Console.WriteLine("a1-a5 are null");
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();

        Console.WriteLine("GC");
        GC.Collect(2, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        Console.ReadKey();
    }
}

class A {
    int[] arr = new int[1000];
    public A() {
        arr[0] = 1;
    }

    public A AProperty { get; set; }
}

}

我使用内置的 Visual Studio 2017 诊断工具制作快照。 初始化 a1 - a5 变量时,堆中有 25 个 A 实例,如预期:

但是在变量a1-a5变为null后,即使额外调用了GC.Collect方法,堆中仍然包含A实例。更多的 GC.Collect 调用不会减少堆中的 A 对象。为什么?有没有办法强制收集所有未使用的对象?

编辑: 我已经切换到 Release 配置,我看到在初始化 a1-a5 变量后堆中有 15 个对象:

在 GC.Collect 调用之后仍然保留 14 个对象。

【问题讨论】:

  • 调试或发布版本?
  • 调试,.Net 4.5
  • 在调试版本中,或者如果你连接了调试器,变量的生命周期,甚至是编译器生成的临时变量,都会延长到方法的末尾,以方便断点和检查变量不再使用的方法。切换到发布版本并重试。
  • 我已切换到发布配置。现在在初始化 a1-a5 变量后,VS 显示'堆中有 15 个对象,分配 null 后仍然保留 14 个 A 实例。
  • 那么你用来查找这些对象的工具可能就像一个调试器。程序集中有元数据(如果我没记错的话)标识变量被认为“正在使用”的方法代码的跨度,这个跨度在最后一次写入或读取后停止。如果代码在 这样的跨度之后执行,则该变量被认为是死的,并且应该忽略它是否具有引用,但是当存在调试器时这些跨度会被扩展,因此该工具可能是表现得像一个人,这让我们一团糟。

标签: c# .net garbage-collection


【解决方案1】:

我不知道 VS 是如何处理这个问题的,但是如果我使用发布版本运行您的代码,我在使用 SOS 检查它时不会在堆上看到任何 A 实例。

要亲眼看看,download WinDbg。然后附加到您的进程并加载 SOS。

.loadby sos clr

您可以使用!dumpheap -stat 转储整个堆,也可以使用!dumpheap -type GCTest.A 仅转储类型。

您应该会看到实例在创建时上升。一旦 GC 回收了这些实例,您就不应该在堆上看到任何 A 实例。

这是所有引用为空后第一次 GC 之后的输出。

0:001> !dumpheap -type GCTest.A
 Address       MT     Size

Statistics:
      MT    Count    TotalSize Class Name
Total 0 objects

【讨论】:

  • 这确实是预期的行为。最有可能在 VS 中使用诊断工具将 JIT 切换到调试模式。
  • @KonradKokosa 可能是。我的意思是这可能是一个工具问题。 PS:很高兴认识你,我期待着阅读你的书:)
  • 很高兴认识你!确实,它只表明在进行如此简单的情景调查时应该多么小心......
猜你喜欢
  • 2014-09-09
  • 1970-01-01
  • 2016-10-10
  • 2014-02-28
  • 2023-03-08
  • 2012-11-10
  • 2021-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多