.NET 4.x:如何强制进行完整的 GC 收集?
.NET 4.x: how to force full GC collection?
当内置调试配置或附加调试器时,似乎有一些更新改变了 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
,并且非常方便编写单元测试来检查某些应该进行 GC 的部分没有被保留。
经过一些 .NET 4.x 更新后,此代码在 .NET 2.x 和 3.x 上成功通过,但在 4.x 的所有变体上均失败。我试图将其称为 GC.Collect(2, GCCollectionMode.Forced, blocking: true)
,在 App.config
和 GCSettings.LatencyMode = GCLatencyMode.Batch
中制作 <gcConcurrent enabled="false"/>
- 没有任何帮助。如果我 运行 没有附加调试器的代码并且它是在发布配置中构建的(即优化) - 它输出 Dead
。否则就是Alive
.
我知道依赖 GC 在生产中不是一个好主意。但是对于测试,我不知道如何替换通过测试检查特定代码片段不会泄漏内存的能力。它是纯测试程序集,我可以转动一些兼容性开关或类似的东西。我的目标是检查我自己的代码,而不是 GC 优化。
有没有办法以某种方式强制 GC 返回以前的行为?
P.S。看到几乎一模一样的question,不过当时和NC运行ch有关。我没有安装它。我 运行 代码甚至来自命令行,根本没有 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
。
这两种变体有时不是很方便,但至少是一致的(Debug
或 Release
配置,是否连接调试器 - 仍然输出 Dead
)。
现在我很好奇当前执行作用域中仅仅存在 WeakReference 变量是如何阻止 GC 清理其 Target 的,以及为什么将它埋在调用堆栈中的作用域中的某个地方不会做同样的事情。
在调试模式下,这应该使对象在所有 .NET 版本中保持活动状态。任何其他都是错误(或缺失的功能)。
您可以通过将一些代码拆分为新方法来禁用此调试帮助。
在发布模式下,这应该表现出您想要的较短的 GC 生命周期。这当然不能保证,但这是一个非常理想的优化。
另一种解决方法是使用 new object[1]
或类似的结构。然后,您可以可靠地清空第一个数组成员。我认为该框架具有 Box
或 StrongBox
类型。不知道叫什么。
您不能真正依赖 IsAlive 属性。它的问题是,只有当它 returns 为假时你才能信任它。
Have a look at Why You Shouldn’t Rely On WeakReference.IsAlive
While a WeakReference points to an object that is either live
(reachable), or garbage (unreachable) that has not yet been collected
by the GC, the IsAlive property will return true. After an object has
been collected, if the WeakReference is short (or if the target object
does not have a finalizer) then IsAlive will return false.
Unfortunately by the time IsAlive returns, the target may have been
collected.
This situation can occur because of the way the GC
suspends all managed threads before scanning the heap for garbage and
collects it (this is an oversimplified explanation for illustrative
purposes). The GC can run at any time between two instructions.
下面的方法是可靠的检查方法。
object a = new object();
WeakReference wr = new WeakReference(a);
object aa = (object)wr.Target;
Console.WriteLine(aa != null ? "Alive" : "Dead");
最简单的方法似乎是使 var a = new object();
成为 class 字段而不是局部变量。使测试不那么孤立,但现在看起来是一致的,并且不会阻止 GC 在方法中间进行收集。
当内置调试配置或附加调试器时,似乎有一些更新改变了 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
,并且非常方便编写单元测试来检查某些应该进行 GC 的部分没有被保留。
经过一些 .NET 4.x 更新后,此代码在 .NET 2.x 和 3.x 上成功通过,但在 4.x 的所有变体上均失败。我试图将其称为 GC.Collect(2, GCCollectionMode.Forced, blocking: true)
,在 App.config
和 GCSettings.LatencyMode = GCLatencyMode.Batch
中制作 <gcConcurrent enabled="false"/>
- 没有任何帮助。如果我 运行 没有附加调试器的代码并且它是在发布配置中构建的(即优化) - 它输出 Dead
。否则就是Alive
.
我知道依赖 GC 在生产中不是一个好主意。但是对于测试,我不知道如何替换通过测试检查特定代码片段不会泄漏内存的能力。它是纯测试程序集,我可以转动一些兼容性开关或类似的东西。我的目标是检查我自己的代码,而不是 GC 优化。
有没有办法以某种方式强制 GC 返回以前的行为?
P.S。看到几乎一模一样的question,不过当时和NC运行ch有关。我没有安装它。我 运行 代码甚至来自命令行,根本没有 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
。
这两种变体有时不是很方便,但至少是一致的(Debug
或 Release
配置,是否连接调试器 - 仍然输出 Dead
)。
现在我很好奇当前执行作用域中仅仅存在 WeakReference 变量是如何阻止 GC 清理其 Target 的,以及为什么将它埋在调用堆栈中的作用域中的某个地方不会做同样的事情。
在调试模式下,这应该使对象在所有 .NET 版本中保持活动状态。任何其他都是错误(或缺失的功能)。
您可以通过将一些代码拆分为新方法来禁用此调试帮助。
在发布模式下,这应该表现出您想要的较短的 GC 生命周期。这当然不能保证,但这是一个非常理想的优化。
另一种解决方法是使用 new object[1]
或类似的结构。然后,您可以可靠地清空第一个数组成员。我认为该框架具有 Box
或 StrongBox
类型。不知道叫什么。
您不能真正依赖 IsAlive 属性。它的问题是,只有当它 returns 为假时你才能信任它。
Have a look at Why You Shouldn’t Rely On WeakReference.IsAlive
While a WeakReference points to an object that is either live (reachable), or garbage (unreachable) that has not yet been collected by the GC, the IsAlive property will return true. After an object has been collected, if the WeakReference is short (or if the target object does not have a finalizer) then IsAlive will return false. Unfortunately by the time IsAlive returns, the target may have been collected.
This situation can occur because of the way the GC suspends all managed threads before scanning the heap for garbage and collects it (this is an oversimplified explanation for illustrative purposes). The GC can run at any time between two instructions.
下面的方法是可靠的检查方法。
object a = new object();
WeakReference wr = new WeakReference(a);
object aa = (object)wr.Target;
Console.WriteLine(aa != null ? "Alive" : "Dead");
最简单的方法似乎是使 var a = new object();
成为 class 字段而不是局部变量。使测试不那么孤立,但现在看起来是一致的,并且不会阻止 GC 在方法中间进行收集。