了解局部变量的垃圾收集器行为
下面是一个非常简单的控制台应用程序(尝试小提琴 ):
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)
将需要在Test
保持活着(如MSDN建议的那样 ),但显然它不是(在上面的代码中注释掉)。
现在,如果我更改下面的代码, callback
会像预期的那样被垃圾收集:
Callback callback = new Callback(); callback = null;
这种情况发生在.NET 4.5.1中。
问题 :我错过了什么吗? 我可以依赖这种行为,还是特定于.NET版本的东西?
@Porges的评论很好地解释了一切:
尝试在发布模式下构建和运行它,而不附加调试器。 我得到了预期的行为,但没有在Debug中。
…
即。 使用Ctrl-F5运行,而不仅仅是F5。 它在.NET 4 / 4.5 / 4.5.1中为我立即收集它。 但是,是的,你不能真正依赖这种行为。
Release版本和Ctrl – F5带回了预期的行为。 我敦促@Porges发布这个作为答案 ,我会投票并接受谢谢。
作为后续行动,我想介绍以下有趣的行为。 现在使用Release + Ctrl-F5,即使我在代码中取消注释// GC.KeepAlive(callback)
行, callback
仍然会被垃圾收集。 显然,这是因为编译器将此行识别为由于while (true)
循环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
在这里非常相关,以防止像上面这样的任何优化。
.NET垃圾收集是不确定的。
您链接到的MSDN页面说明了这一切 – 强调添加:
KeepAlive方法的目的是确保存在对可能被垃圾收集器过早回收的对象的引用。
仅仅因为callback
可以在从Test
返回范围之前进行垃圾收集并不意味着它将会是。