C#笔记——垃圾回收与资源管理
本文最后更新于:2023年11月12日 晚上
垃圾回收与资源管理
析构器
其实就是析构方法。语法就是类名前加一个~,比如~ClassName(){ ... }
最重要的一点,析构器只有在对象被垃圾回收时才运行
编译器内部自动将析构器转换成对Object.Finalize方法的一个重写版本的调用,比如:
1 | |
就等价于:
1 | |
垃圾回收
垃圾回收什么时候进行?
可以确定的是,在对象不再需要的时候进行。但是垃圾回收不一定在对象不再需要之后立刻进行。CLR会自行判断执行垃圾回收的时机。
例如,在它认为可用内存不够的时候,或者堆的大小超过系统定义的阈值的时候。
垃圾回收器的工作原理
大体步骤如下:
- 构造所有可达对象的一个映射(map)。为此,它会反复跟随对象中的引用字段。垃圾回收器会非常小心地构造映射,确保循环引用不会造成无限递归。任何不在映射中的对象肯定不可达。
- 检查是否有任何不可达对象包含一个需要运行的析构器(运行析构器的过程称为“终结”)。需终结的任何不可达对象都放到一个称为freachable(F-reachable)的特殊队列中。
- 回收剩下的不可达对象(即不需要终结的对象)。为此,它会在堆中向下面移动可达的对象,对堆进行“碎片整理”,释放位于堆顶部的内存。一个可达对象被移动之后,会更新对该对象的所有引用。
- 然后,允许其他线程恢复执行。
- 在一个独立的线程中,对需要终结的不可达对象执行终结操作。
资源管理
当我们使用某些资源时,比如内存、数据库、文件句柄等这种稀缺资源,应该尽快释放,所以这个时候就需要我们手动释放资源。
即通过自己写的资源清理(disposal)方法来实现资源释放。可显式调用类的资源清理方法,从而控制释放资源的时机。
简单举个例子,比如我们使用TextReader类来从顺序输入流读取字符时,读取完毕后应该及时调用Close()方法释放资源:
1 | |
但是它其实不是异常安全的,因为如果在reader.ReadLine()或其他地方出现异常的话,就不会执行reader.Close()释放文件句柄了。
或许我们会想到try...catch...finally...语句,这确实是一种解决方法,然而也存在更好的方案。
using语句
using语句提供了一个脉络清晰的机制来控制资源的生存期。可以创建一个对象,这个对象会在using语句块结束时销毁。
using语句的用法如下:
1 | |
它等价于下面的代码块:
1 | |
这也说明using语句声明的变量必须实现IDisposable接口。IDisposable接口在System命名空间中,只包含一个名为Dispose的方法。Dispose方法的作用是清理对象使用的任何资源。
IDisposable接口的实现
下面是实现IDisposable接口的类的推荐写法:
1 | |
下面解释一下这么写的好处:
- 受保护的
Dispose方法可以安全地多次调用,因为变量disposed指出方法以前是否运行过,这样可以防止在并发调用方法时资源被多次清理。 - 受保护的
Dispose方法支持托管资源(比如大的数组)和非托管资源(比如文件句柄)的清理。如果disposing参数为true,该方法肯定是从公共Dispose()方法中调用的,所以托管和非托管资源都会被释放。如果disposing参数为false,该方法肯定是从析构器中调用的,而且垃圾回收器正在终结对象,所以不需要释放托管资源,因为它们将由(或者已经由)垃圾回收器处理:在这种情况下只需要释放非托管资源。 - 静态
GC.SuppressFinalize方法可以阻止垃圾回收器为这个对象调用析构器,因为对象已经终结
这里还有另外一种线程安全的方式写代码:
1 | |
lock语句旨在阻止一个代码块同时在不同线程上运行。像这样使用锁能确保线程安全,但对性能有一些影响。

希望本文章能够帮到您~