person p1 = new person();
p1.Name = "Tam";
WeakReference wkr = new WeakReference(p1);
p1 = null;
GC.Collect(); // 强制进行垃圾回收
object wP1 = wkr.Target;
if (wP1 != null)
{
Console.WriteLine(((person)wP1).Name);
}
else
{
Console.WriteLine("对象已被回收");
}
Console.ReadKey();
}
}
class person
{
public string Name { get; set; }
}
p1=Null的时候,代表p1之前指向的内存已经可以被回收了吧?我测试为什么还能打印出来Name呢
------解决思路----------------------
我试验了下,clr 2 和 clr 4 的结果并不一致。这个和 jit / gc 实现有关,不同的编译器和 clr 版本都可能导致结果差异。而且也没有官方的文档说这么细节的实现,能看到的 coreclr 的 ryujit 这一部分也可能和原来不一样。所以影响它的因素具体还有多少真很难说。
不过这并不是一个值得纠结的问题,如果你真的能在实际的代码中遇到 gc 无法回收,导致内存泄漏的问题,那时再去专门针对具体的场景进行分析处理。绝大多数情况下,gc 都能很好的工作,不需要手动 Collect。
从你这个问题里,需要了解的就是两件事:1. jit 在翻译方法的时候会生成一张表,计算出局部变量的生命周期结束的指令位置,以供 gc 参考。2. 把局部变量设置为 null 并不代表原引用立即失效,它的生命周期是 jit 计算出来的,未必是设置为 null 的位置。(背后的原因是:调用栈上方法的参数/局部变量也是一种 gc root,生命周期与 jit 生成的指令有关,实际的栈和寄存器对内存地址的引用并不一定是 c# 代码想当然的翻译)
另外,希望不要被有些说法误导。无参的 GC.Collect 对于一般对象的回收是阻塞的(有参数的重载可以进行非阻塞 gc),只不过是因为有析构器队列机制,如果需要回收的对象有析构器,那么会放到一个队列里,在 gc 的线程执行。这时,完全回收可以使用:
你使用多线程看到了回收的结果,不要理解错了。那是因为方法中代码改变了,会影响到 jit 计算的生命周期,而不是因为需要等待 GC.Collect 执行。你可以试验,仅仅把测试是否回收的代码移到另一个方法里就能改变结果。
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();