第十四章 垃圾回收
1.值类型
OutputValues方法,输出方法内部变量。
public void OutputValues()
{
int i = 1;
int j = 99;
int k = i;
Console.WriteLine($"i: {i}");
Console.WriteLine($"j: {j}");
Console.WriteLine($"k: {k}");
}
变量i, j , k是基本数据类型,在声明时就能确定它的内存大小,它们是值类型变量,数据项直接存储在栈上。
栈,只能在末尾添加或删除的线性表。添加元素称为入栈,删除则为出栈。最新入栈的元素,总是最后出栈,相当于往箱子里放东西,最新放进去的总是在最地下,最上面的最容易拿出来。"先进后出,后进先出"。
方法OutputValues被调用时,变量i, j, k它们在栈内存中:

方法OutputValues结束调用时,变量i, j, k它们在栈内存中:

值类型变量离开作用域,自动回收内存。
2.引用类型
引用类型变量存储在栈内存,它刚好可以容纳一个堆内存地址。
使用new关键字创建对象分配堆内存,引用类型变量持有对象的一个引用("电话号码")。
class Fanda
{
~Fanda() // 析构器
{
Console.WriteLine("销毁对象");
}
}
public void OutputValues()
{
int i = 2;
Fanda fanda; // 不分配堆内存
int j = i;
fanda = new Fanda(); // 分配Fanda对象堆内存,fanda持有对象的引用
......
}
方法OutputValues被调用时,变量i, fanda, j在内存中的生命周期:

引用类型变量和值类型变量类似,离开作用域自动回收,被引用的对象则不同。对象是用new关键字创建的,但应该在什么时候,采用什么方式回收?
当对象没有人引用了,这个对象可被回收,具体回收时机由CLR (Common Language Runtime公共语言运行时) 决定。
使用析构器或Dispose方法,在对象被销毁前执行清理工作。
3.析构器
当没有变量引用对象时,CLR会在它认为合适的时间销毁对象。销毁对象并将内存归还给堆的过程称为垃圾回收。
CLR回收对象分两步走:
a:CLR执行清理工作,可以写一个析构器来加以控制。析构器总是由垃圾回收器调用。
b:CLR将对象占用的内存归还给堆,解除对象内存分配。
class Cake
{
private Stopwatch sw;
public Cake()
{
sw = Stopwatch.StartNew();
Console.WriteLine("初始化对象");
}
public void ShowDuration()
{
Console.WriteLine("对象实例: {0} ,时间点: {1}", this, sw.Elapsed);
}
~Cake() // 析构器
{
Console.WriteLine("销毁对象");
sw.Stop();
Console.WriteLine("对象实例: {0} ,时间点: {1}", this, sw.Elapsed);
}
/*
编译器内部自动将析构器转换成Object.Finalize方法的一个重写版本的调用。
~Cake方法会转换成以下形式:
protected override void Finalize()
{
try
{
Console.WriteLine("销毁对象");
sw.Stop();
Console.WriteLine("对象实例: {0} ,时间点: {1}", this, sw.Elapsed);
}
finally{ base.Finalize();}
}
*/
}
public static void Main(string [] args)
{
ExampleClass ec = new ExampleClass();
ec.ShowDuration();
}
// 输出:
//初始化对象
//对象实例: Program+Cake ,时间点: 00:00:00.0005936
//销毁对象
//对象实例: Program+Cake ,时间点: 00:00:00.0034213
4.清理非托管资源
在设计一个类时,若使用了非托管资源,需要显式释放非托管资源。最常用的非托管资源是包装了系统资源的对象,例如,文件句柄,窗口句柄,网络连接等,虽然垃圾回收器可以跟踪非托管资源的创建的对象,但却无法具体了解如何清理资源。创建非托管资源对象时,建议在Dispose方法中提供必要的代码清理非托管资源,通过提供Dispose方法,使用完对象后可以显示的释放所有资源(主动调用Dispose方法)。
//
// 1.IDisposable接口在System命名空间中,只包含一个名为Dispose方法。
// 2.Dispose方法的作用是清理对象使用的任何资源。
//
namespace System
{
interface IDisposable
{
void Dispose();
}
}
class TestDispose : IDisposable
{
private IntPtr handle; // 句柄;非托管资源。
private Component component = new Component();// 常规对象;托管资源。
private bool disposed = false;
public TestDispose(IntPtr intptr)
{
this.handle = intptr;
}
// 释放TestDispose对象的资源,IDisposable接口方法的实现
public void Dispose()
{
// 无论是主动调用Dispose方法,还是等待CLR调用析构方法,非托管资源都应该被释放。
// 主动调用对象的Dispose方法:
// 1.清理对象的所有资源。析构器还会自动清理一次,应避免重复清理。
// 2.主动释放的是对象的其它托管资源,非托管资源是必须要被释放。
//
Dispose(true);
// 因为是主动调用TestDispose对象的Dispose方法,所以不能把对象本身给回收了。
// 请求CLR不要运行指定对象的终结器。
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if ( !disposed ) // 是否执行过垃圾回收。否,执行清理。
{
if (disposing)
{
// 决定释放需要释放对象的其它托管资源。
// 因为是托管资源,CLR会决定何时回收对象,所以可以主动回收,也可以懒人自动回收。
component.Dispose();
}
// 释放非托管资源
// CLR无法具体了解如何清理,必须要显示的提供释放非托管资源的代码。
CloseHandle(handle);
handle = IntPtr.Zero;
disposed = true;
}
}
[System.Runtime.InteropServices.DllImport("Kernel32")]
private extern static Boolean CloseHandle(IntPtr handle);
~TestDispose()
{
Dispose(false);
}
}
public static void Mian(string [] args)
{
string str = "hello world!";
IntPtr ptr = Marshal.StringToHGlobalAnsi(str);
TestDispose my = new TestDispose(ptr);
my.Dispose();
}
5.异常安全的资源清理
为了确保资源清理方法总是得到调用,无论是否发生异常,一个办法是在finally块中调用该方法。使用finally语句块可行,但它也存在缺点,如要释放多个资源可能需要try/finally嵌套。
using语句提供了一个脉络清晰的机制来控制资源的生存期。可创建一个对象,该对象在using语句块结束时销毁。
using语句声明的变量的类型必须实现IDisposable接口。IDisposable接口在System命名空间中,只包含一个名为Dispose的方法。
using(TextReader reader = new SteamReader(filename))
{
string line;
while((line = reader.ReaderLine()) != null)
{
Console.WriteLine(line);
}
}
// 这个using语句完全等价于以下形式:
{
TextReader reader = new StreamReader(filename);
try
{
string line;
while((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
finally
{
if(reader != null)
{
((IDisposable).reader).Dispose();
}
}
}

浙公网安备 33010602011771号