【问题标题】:.NET 4.x: how to force full GC collection?.NET 4.x:如何强制进行完整的 GC 收集?
【发布时间】:2015-09-30 22:35:50
【问题描述】:

当内置调试配置或附加调试器时,似乎一些更新改变了 GC 行为:

//Code snippet 1
var a = new object();
var w = new WeakReference(a);
a = null;

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.WriteLine(w.IsAlive ? "Alive" : "Dead");

这样的代码用于打印Dead,并且对于编写单元测试检查是否应该被GCed的某些部分没有被持有非常方便。

在 .NET 4.x 更新后,此代码在 .NET 2.x 和 3.x 上成功通过,但在 4.x 的所有变体上都失败。我试图将其称为GC.Collect(2, GCCollectionMode.Forced, blocking: true),在App.configGCSettings.LatencyMode = GCLatencyMode.Batch 中创建<gcConcurrent enabled="false"/> - 没有任何帮助。如果我在没有附加调试器的情况下运行代码并且它是在发布配置中构建的(即带有优化) - 它输出Dead。否则为Alive

我知道在生产环境中依赖 GC 并不是一个好主意。但是对于测试,我不知道如何替换通过测试检查特定代码段不会泄漏内存的能力。这是纯粹的测试组装,我可以打开一些兼容性开关或类似的东西。我的目标是检查我自己的代码,而不是 GC 优化。

有没有办法以某种方式强制 GC 执行先前的行为?

附:我看到几乎相同的question,但当时它与NCrunch有关。我没有安装它。我什至从命令行运行代码,根本没有 VS,结果相同。


UPD:我发现,如果我将分配和设置对 null 的引用的代码移动到单独的方法中 - 但它始终输出 Dead

//Code snippet 2
internal class Program
{
    private static void Main(string[] args)
    {
        var w = DoWorkAndGetWeakRef();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        Console.WriteLine(w.IsAlive ? "Alive" : "Dead");
        Console.ReadLine();
    }

    private static WeakReference DoWorkAndGetWeakRef()
    {
        var a = new object();
        var w = new WeakReference(a);
        a = null;
        return w;
    }
}

如果我转移到单独的方法 GC 收集调用和 WeakReference 检查,结果相同:

//Code snippet 3
internal class Program
{
    private static void Main(string[] args)
    {
        var a = new object();
        var w = new WeakReference(a);
        a = null;

        CollectAndCheckWeakRef(w);
        Console.ReadLine();
    }

    private static void CollectAndCheckWeakRef(WeakReference w1)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        Console.WriteLine(w1.IsAlive ? "Alive" : "Dead");
    }
}

尽管看起来很重要的一点是原始w 变量不在当前范围内。如果我将Console.WriteLine(w1.IsAlive ? "Alive" : "Dead"); 移回Main - 它再次变为Alive

这两种变体有时不是很方便,但至少是一致的(DebugRelease 配置,调试器是否附加 - 仍然输出 Dead)。

现在我很好奇在当前执行范围内仅仅存在 WeakReference 变量是如何阻止 GC 清理其目标的,以及为什么将它放在埋在调用堆栈中的某个范围内的某个位置并不会起到同样的作用。

【问题讨论】:

  • @HansPassant 我知道 GC 能够收集未使用的局部变量,即使该方法仍在运行。这里的问题是为什么即使引用为空并明确要求它也不收集它?我已经构建了没有最新更新的代理,测试正在使用它并且已经通过了几年。在我的本地机器和新的构建代理上,由于某种原因,它确实失败了。
  • Hmya,专注于发布为每个人打印“Alive”的复制代码。
  • @HansPassant here 是在安装了 VS2015 的 Win8.1/Win10 上打印 Alive 的二进制文件,在单独安装了 VS2010/VS2013 和 4.5.1 的 WinServer2008R2 上打印 Dead。建立在 Win8.1/VS2015 的调试配置中,来自有问题的源代码。除了最后的Console.ReadLine()。 (我不知道如何准确检查 .NET 版本 - 有了所有这些就地更新,这并不容易......你能指导我去哪里看吗?)
  • 嗯,StrongBox,把它隐藏在你的 sn-p 中做得很好。那有什么意义呢?
  • @HansPassant 哎呀,抱歉,这是@usr 建议的带有StrongBox<T> 的版本——虽然没有改变,但为了完整起见here 也是原来的版本。跨度>

标签: c# .net garbage-collection


【解决方案1】:

在调试模式下,这应该使对象在所有 .NET 版本中保持活动状态。其他任何东西都是错误(或缺失的功能)。

您可以通过将一些代码拆分为新方法来禁用此调试帮助。

在发布模式下,这应该表现出您想要的较短的 GC 生命周期。这当然不能保证,但这是一个非常理想的优化。

另一种解决方法是使用new object[1] 或类似的构造。然后,您可以可靠地清空第一个数组成员。我认为该框架具有BoxStrongBox 类型。不知道叫什么。

【讨论】:

  • 程序也应该在没有调试器的情况下运行(Ctrl+F5 而不是 F5)。
  • 尝试了数组和StrongBox<object> - 结果相同
  • @IvanDanilov 可能 new object 最终会出现在 JIT 级别的局部变量中。将框的创建移至工厂方法。
  • static object CreateObject() { return new object(); }的调用替换new object()也没有改变。
  • @IvanDanilov 我刚刚在 Debug 和 Release 中测试了两个代码 sn-ps。 VS13 32 位。所有案例都按预期工作。什么样的场景让你感到意外?
【解决方案2】:

您不能真正依赖 IsAlive 属性。它的问题在于,只有当它返回 false 时,您才能信任它。

Have a look at Why You Shouldn’t Rely On WeakReference.IsAlive

虽然 WeakReference 指向的对象要么是活动的 (可达),或尚未收集的垃圾(不可达) 通过 GC,IsAlive 属性将返回 true。在一个对象拥有之后 如果 WeakReference 很短(或者如果目标对象 没有终结器),那么 IsAlive 将返回 false。 不幸的是,当 IsAlive 返回时,目标可能已经 收集。

这种情况可能会因为 GC 的方式而发生 在扫描堆中的垃圾之前挂起所有托管线程,并且 收集它(这是一个过于简单的解释说明 目的)。 GC 可以在两条指令之间的任何时间运行。

以下方法是可靠的检查方法。

object a = new object();
WeakReference wr = new WeakReference(a);

object aa = (object)wr.Target;

Console.WriteLine(aa != null ? "Alive" : "Dead");

【讨论】:

  • 他希望它返回 false。
  • 作者在检查对象是否存活后不想使用它。在这种情况下,两种变体是等价的。
【解决方案3】:

似乎最简单的方法是使var a = new object(); 成为类字段而不是局部变量。使测试不那么孤立,但现在看起来是一致的,并且不会阻止 GC 在方法中间收集。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多