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
语句旨在阻止一个代码块同时在不同线程上运行。像这样使用锁能确保线程安全,但对性能有一些影响。
希望本文章能够帮到您~