为了在已经经常发生内存泄漏的地方检测潜在的内存泄漏,我使用了如下所示的构建测试。主要思想是拥有一个实例,不再引用它,让垃圾收集器收集它。我不想关注这是否是一种好技术(在我的特殊情况下,它做得很好),但我想关注以下问题:
下面的代码在。net framework 4.8上可以完美地工作,但在。net 5上却不能。为什么?
[Test]
public void ConceptualMemoryLeakTest()
{
WeakReference weakReference;
{
object myObject = new object();
weakReference = new WeakReference(myObject);
myObject = null;
Assert.Null(myObject);
}
Assert.True(weakReference.IsAlive); // instance not collected by GC
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();
Assert.False(weakReference.IsAlive); // instance collected by GC
}
您可以看到,主要思想是使用WeakReference
并使用IsAlive
来确定实例是否被GC删除。新CLR(源于。net core的规则)中的规则是如何变化的?我知道这里所做的并不依赖于特定的东西。相反,我只是利用了我在NetFramework 4.8中看到的CLR的行为。
你有一个想法如何得到类似的东西,也适用于。net 5吗?
原因可能是分层编译。简而言之,分层编译将(对于某些条件下的某些方法)首先编译方法的粗糙、低优化版本,然后在必要时准备更好的优化版本。这在。net 5(和。net Core 3+)中默认启用,但在。net 4.8中不可用。
在你的情况下,结果是你的方法是用前面提到的"quick"编译的。编译和没有足够的优化为您的代码工作,如您所期望的(即myObject
变量的生命周期延长,直到方法结束)。即使您在启用了优化的发布模式下编译,并且没有附加任何调试器,情况也是如此。
可以通过添加:
来禁用分层编译<TieredCompilation>false</TieredCompilation>
在你的。net 5项目的csproj文件中的一些<PropertyGroup>
,然后你会观察到你在。net 4.8情况下的相同行为。
另一种选择(除了将变量移动到另一个方法并从中返回WeakReference
)是使用:
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
ConceptualMemoryLeakTest
方法的属性。
实际上,通过在评论和回答中提供的提示,我意识到将实例移动到一个单独的方法并防止内联它是有效的:
[MethodImpl(MethodImplOptions.NoInlining)]
private WeakReference CreateWeakReference()
{
object myObject = new object();
return new WeakReference(myObject);
}
[Test]
public void ConceptualMemoryLeakTest()
{
WeakReference weakReference = CreateWeakReference();
Assert.True(weakReference.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();
GC.Collect();
Assert.False(weakReference.IsAlive);
}
不需要
<TieredCompilation>false</TieredCompilation>