第十四章 垃圾回收

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();
        }
    }
}
posted @ 2021-02-21 11:29  葡式蛋挞  阅读(15)  评论(0)    收藏  举报