为什么C#中的Lambda表达式会导致内存泄漏? [英] Why does a lambda expression in C# cause a memory leak?
问题描述
注意:这不仅是一些随机的无用代码,也是尝试重现Lambda表达式和C#中的内存泄漏的问题.
Note: this is not just some random useless code, this is an attempt to reproduce an issue with lambda expressions and memory leaks in C#.
检查以下C#程序.这是一个控制台应用程序,只需:
Examine the following program in C#. It's a console application that simply:
- 创建一个Test类型的新对象
- 在控制台上写入对象已创建
- 调用垃圾回收
- 等待任何用户输入
- 闭嘴
我使用JetBrains DotMemory运行此程序,并拍摄了两个内存快照:一个在对象初始化后,另一个在对象被收集后.我比较了快照并得到了期望的结果:一个类型为Test的死对象.
I run this program using JetBrains DotMemory, and I take two memory snapshots: one after the object was initialized, and another after its been collected. I compare the snapshots and get what I expect: one dead object of type Test.
但这是一个难题:然后,我在对象的构造函数中创建一个本地lambda表达式,并且我在任何地方都不使用它.它只是一个局部构造函数变量.我在DotMemory中运行相同的过程,突然,我得到一个类型为Test +<>的对象,该对象可以在垃圾回收后幸免.
But here's the quandary: I then create a local lambda expression inside the object's constructor and I DO NOT USE IT ANYWHERE. It's just a local constructor variable. I run the same procedure in DotMemory, and suddenly, I get an object of type Test+<>, which survives garbage collection.
请参阅DotMemory随附的保留路径报告:lambda表达式具有指向Test +<对象的指针,这是预期的.但是谁有一个指向lambda表达式的指针,为什么将其保存在内存中?
See the attached retention path report from DotMemory: The lambda expression has a pointer to the Test+<> object, which is expected. But who has a pointer to the lambda expression, and why is it kept in memory?
另外,这个Test +<>对象-我假设它只是持有lambda方法的临时对象,与原始Test对象无关,对吗?
Also, this Test+<> object - I assume it is just temporary object to hold the lambda method, and has nothing to do with the original Test object, am I right?
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();
}
}
推荐答案
如果使用某些东西(例如dotpeek)对代码进行反编译,则会看到编译器生成了以下内容:
If you decompile your code with something (like dotpeek), you will see that compiler generated something like this:
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;
}
}
}
因此它创建了子类,将您的函数作为该类的实例方法,在 static 字段中创建了该类的单例实例,最后使用以下命令创建了 static 字段:您的Func<object,bool
引用方法DelegateFunc
.因此,GC不能收集编译器生成的那些静态成员也就不足为奇了.当然,这些对象不会为您创建的每个Test
对象创建,只能创建一次,因此我不能真正称其为泄漏".
So it created child class, put your function as a instance method of that class, created singleton instance of that class in a static field and finally created static field with your Func<object,bool
referencing method DelegateFunc
. So no surprise that those static members generated by compiler cannot be collected by GC. Of course those objects are not created for each Test
object you create, only once, so I cannot really call that a "leak".
这篇关于为什么C#中的Lambda表达式会导致内存泄漏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!