了解局部变量的垃圾收集器行为

时间:2014-03-19 03:47:52

标签: c# .net garbage-collection

下面是一个非常简单的控制台应用程序(尝试fiddle):

using System;
using System.Threading;
using System.Threading.Tasks;

public class ConsoleApp
{
    class Callback
    {
        public Callback() { }
        ~Callback() { Console.WriteLine("~Callback"); }
    }

    static void Test(CancellationToken token)
    {
        Callback callback = new Callback();

        while (true)
        {
            token.ThrowIfCancellationRequested();

            // for the GC
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();

            Thread.Sleep(100);
        }

        // no need for KeepAlive?
        // GC.KeepAlive(callback);      
    }

    public static void Main()
    {
        var cts = new CancellationTokenSource(3000);
        try
        {
            Test(cts.Token);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Enter to exit...");
        Console.ReadLine();
    }
}

这里,callback对象在超出Test方法的范围之前不会被垃圾收集。

我认为GC.KeepAlive(callback)需要在TestMSDN suggests)内保持活着,但显然不是(在上面的代码中注释掉)。

现在,如果我更改下面的代码,callback会按预期收集垃圾:

Callback callback = new Callback();
callback = null;

.NET 4.5.1会发生这种情况。

问题:我错过了什么吗?我可以依赖这种行为,还是.NET版本特定的?

2 个答案:

答案 0 :(得分:3)

@Porges'评论很好地解释了一切:

  

尝试建设&在发布模式下运行它,不附加调试器。我得到了预期的行为,但没有在Debug中   ...
  即。使用Ctrl-F5运行,而不仅仅是F5。它在.NET 4 / 4.5 / 4.5.1中为我立即收集它。但是,是的,你不能真正依赖这种行为。

发布版本和 Ctrl - F5 带回了预期的行为。 我敦促@Porges发布这个作为答案,我会投票并接受谢谢。

作为后续行动,我想提出以下有趣的行为。现在使用Release + Ctrl-F5,即使我在代码中取消注释// GC.KeepAlive(callback)行,callback仍然会被垃圾收集。显然,这是因为编译器将此行视为由于while (true)循环而无法访问,但仍未在callback上发出强引用。

以下是正确的模式:

static void Test(CancellationToken token)
{
    Callback callback = new Callback();

    try
    {
        while (true)
        {
            token.ThrowIfCancellationRequested();

            // for the GC
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();

            Thread.Sleep(100);
        }
    }
    finally
    {
        GC.KeepAlive(callback);     
    }
}

查看GC.KeepAlive实现:

也很有趣
[MethodImpl(MethodImplOptions.NoInlining)]
public static void KeepAlive(object obj)
{
}

正如预期的那样,它什么都不做,只是服务器作为编译器的提示来生成IL代码,该代码保持对对象的强引用,直到调用KeepAlive为止。 MethodImplOptions.NoInlining与此非常相关,可以阻止上述任何优化。

答案 1 :(得分:1)

.NET垃圾收集是不确定的。

MSDN page to which you linked说明了一切 - 重点是:

  

KeepAlive方法的目的是确保存在对垃圾收集器过早回收的风险的对象的引用。

仅仅因为callback在从Test返回时超出范围之前可以进行垃圾回收并不意味着它会。