use*_*248 8 c# lambda memory-leaks
注意:这不仅仅是一些随机无用的代码,这是尝试在C#中重现lambda表达式和内存泄漏的问题.
在C#中检查以下程序.这是一个简单的控制台应用程序:
我使用JetBrains DotMemory运行这个程序,我带了两个内存快照:一个在初始化对象后,另一个在收集后.我比较了快照并得到了我所期望的:一个类型为Test的死对象.
但这是窘境:然后我在对象的构造函数中创建一个本地lambda表达式,我不会在任何地方使用它.它只是一个本地构造函数变量.我在DotMemory中运行相同的过程,突然间,我得到了一个Test + <>类型的对象,它可以在垃圾回收中幸存下来.
请参阅DotMemory附带的保留路径报告:lambda表达式有一个指向Test + <>对象的指针,这是预期的.但谁有指向lambda表达式的指针,为什么它保存在内存中?
另外,这个Test + <>对象 - 我认为它只是暂存对象来保存lambda方法,并且与原始的Test对象无关,我是对的吗?
public class Test
{
public Test()
{
// this line causes a leak
Func<object, bool> t = _ => true;
}
public void WriteFirstLine()
{
Console.WriteLine("Object allocated...");
}
public void WriteSecondLine()
{
Console.WriteLine("Object deallocated. Press any button to exit.");
}
}
class Program
{
static void Main(string[] args)
{
var t = new Test();
t.WriteFirstLine();
Console.ReadLine();
t.WriteSecondLine();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.ReadLine();
}
}
Run Code Online (Sandbox Code Playgroud)
Evk*_*Evk 11
如果你用某些东西(比如dotpeek)反编译你的代码,你会发现编译器生成了这样的东西:
public class Test {
public Test() {
if (Test.ChildGeneratedClass.DelegateInstance != null)
return;
Test.ChildGeneratedClass.DelegateInstance =
Test.ChildGeneratedClass.Instance.DelegateFunc;
}
public void WriteFirstLine() {
Console.WriteLine("Object allocated...");
}
public void WriteSecondLine() {
Console.WriteLine("Object deallocated. Press any button to exit.");
}
[CompilerGenerated]
[Serializable]
private sealed class ChildGeneratedClass {
// this is what's called Test.<c> <>9 in your snapshot
public static readonly Test.ChildGeneratedClass Instance;
// this is Test.<c> <>9__0_0
public static Func<object, bool> DelegateInstance;
static ChildGeneratedClass() {
Test.ChildGeneratedClass.Instance = new Test.ChildGeneratedClass();
}
internal bool DelegateFunc(object _) {
return true;
}
}
}
Run Code Online (Sandbox Code Playgroud)
因此,它创建了子类,将您的函数作为该类的实例方法,在静态字段中创建该类的单例实例,最后使用您的引用方法创建静态字段.毫无疑问,GC无法收集编译器生成的静态成员.当然,这些对象不是为您创建的每个对象创建的,只是一次,因此我无法将其称为"泄漏".Func<object,boolDelegateFuncTest
我怀疑您看到的是编译器优化的效果。
假设Test()被多次调用。编译器每次都可以创建一个新的委托——但这似乎有点浪费。lambda 表达式既不捕获this局部变量或参数,也不捕获任何局部变量或参数,因此可以将单个委托实例重用于Test(). 编译器发出代码来懒惰地创建委托,但将其存储在静态字段中。所以它是这样的:
private static Func<object, bool> cachedT;
public Test()
{
if (cachedT == null)
{
cachedT = _ => true;
}
Func<object, bool> t = cachedT;
}
Run Code Online (Sandbox Code Playgroud)
现在这确实创建了一个永远不会被垃圾收集的对象,但如果Test频繁调用它会降低 GC 压力。不幸的是,编译器无法真正知道哪个可能更好。
通过查看由 lambda 表达式产生的委托,可以通过引用相等性检测到这一点。例如,这会打印 True(至少对我而言;这是一个编译器实现细节):
using System;
class Test
{
private Func<object> CreateFunc()
{
return () => new object();
}
static void Main()
{
Test t = new Test();
var f1 = t.CreateFunc();
var f2 = t.CreateFunc();
Console.WriteLine(ReferenceEquals(f1, f2));
}
}
Run Code Online (Sandbox Code Playgroud)
但是,如果您将 lambda 表达式更改为() => this;它会打印 False。