# 前言
这两天又复习了下垃圾回收机制,在看到 析构函数 的时候,突然想到:析构函数是在真正垃圾回收时才会执行的,那么是否可以用这个来测试一个对象,什么情况下可以立即被垃圾回收?或者说变成垃圾对象。
例如,使用完毕后立即赋值为空,它变成垃圾对象了吗?还是其它什么时机才会。
测试方式为:在不同场景下,调用 GC.Collect ,并查看运行结果:析构函数是否被调用。
# 准备
工具:
- VS2022
- .Net6.0
- 测试类:
class TestClass | |
{ | |
public void DoSomething() | |
{ | |
Console.WriteLine("Doing!"); | |
} | |
~TestClass() | |
{ | |
Console.WriteLine("被垃圾被回收了!"); | |
} | |
} |
# 测试
# 1)情景一:方法内部定义对象使用后赋值为空
方法内立即调用 GC.Collect ()
class MainClass | |
{ | |
/// <summary> | |
/// 执行测试 | |
/// </summary> | |
public void DoTest() | |
{ | |
TestMethod(); | |
Console.ReadKey(); | |
} | |
void TestMethod() | |
{ | |
TestClass test = new TestClass(); | |
test.DoSomething(); | |
test = null; | |
GC.Collect(); | |
} | |
} |
打印结果:
Doing!
析构函数未被调用。
将 GC.Collect () 方法调用移动到 DoTest () 方法的 TestMethod () 调用之后:
Doing!
被垃圾被回收了!
析构函数被调用了。
将 test = null
去掉,依然执行了析构函数。
# 结论:方法块内的代码,使用完毕后无论是否赋值为空,都不会立即变成垃圾,当方法块结束后才会自动成为垃圾对象,能够被垃圾回收处理。
# 2)情景二:全局变量方法内部使用后赋值为空
方法内立即调用 GC.Collect ()
class MainClass | |
{ | |
private TestClass _test; | |
/// <summary> | |
/// 执行测试 | |
/// </summary> | |
public void DoTest() | |
{ | |
TestMethod(); | |
Console.ReadKey(); | |
} | |
void TestMethod() | |
{ | |
_test = new TestClass(); | |
_test.DoSomething(); | |
_test = null; | |
GC.Collect(); | |
} | |
} |
打印结果:
Doing!
析构函数未被调用。
将 GC.Collect () 方法调用移动到 DoTest () 方法的 TestMethod () 调用之后:
Doing!
被垃圾被回收了!
析构函数被调用了。
# 结论:全局变量方法内部使用后赋值为空,在该方法结束后,才会变成垃圾对象。
# 3)情景三:委托方法中包含对象
class MainClass | |
{ | |
/// <summary> | |
/// 执行测试 | |
/// </summary> | |
public void DoTest() | |
{ | |
Action callback = TestMethod; | |
callback.Invoke(); | |
GC.Collect(); | |
GC.WaitForPendingFinalizers(); | |
} | |
void TestMethod() | |
{ | |
TestClass test = new TestClass(); | |
test.DoSomething(); | |
} | |
} |
打印结果:
Doing!
被垃圾被回收了!
析构函数被调用了。
此处改成:
Action callback = () => | |
{ | |
TestClass test = new TestClass(); | |
test.DoSomething(); | |
}; |
结果一致。
# 结论:委托、匿名方法本身新建的局部变量,会按照正常的方法调用规则在调用结束后释放。
# 4)情景四:匿名方法中包含对象
class MainClass | |
{ | |
/// <summary> | |
/// 执行测试 | |
/// </summary> | |
public void DoTest() | |
{ | |
TestMethod().Invoke(); | |
GC.Collect(); | |
GC.WaitForPendingFinalizers(); | |
} | |
Action TestMethod() | |
{ | |
TestClass test = new TestClass(); | |
//test.DoSomething(); | |
return () => test.DoSomething(); | |
} | |
} |
打印结果:
Doing!
析构函数未被调用,对象生命周期应该变成 DoTest 的了。
测试方法如下,在调用 DoTest 方法处再进行 GC.Collect 调用:
MainClass main = new MainClass(); | |
main.DoTest(); | |
GC.Collect(); | |
GC.WaitForPendingFinalizers(); |
则执行了析构函数。
缓存为类的全局变量:
Action callback; | |
/// <summary> | |
/// 执行测试 | |
/// </summary> | |
public void DoTest() | |
{ | |
callback = TestMethod(); | |
callback.Invoke(); | |
callback = null; | |
GC.Collect(); | |
GC.WaitForPendingFinalizers(); | |
} |
未执行析构函数,除非与上述一样在 DoTest 执行完毕后调用 GC.Collect。
# 结论:匿名方法中引用了局部变量,会增加局部变量对象生命周期,提升至委托的调用 (存放) 级别,规则与普通对象一致 —— 委托不被置为空,变量也会一直存活。
# 总结
在方法块内对某个对象赋值为空,不管是全局还是局部变量,该对象并不能立刻成为『垃圾』,至少等该方法块执行完毕后,才可能能够被回收。
若存在匿名方法使用了局部变量,其生存周期与委托绑定。