垃圾回收
垃圾回收是指由回收不再被引用的对象所占用的内存。
垃圾回收器只回收内存,不处理其他资源,比如数据库连接、句柄(文件、窗口等)、网络端口以及硬件设备(比如串口)。
.NET垃圾回收原理
.NET 的垃圾回收器采用mark-and-compact算法。一次垃圾回收过程开始时,垃圾回收器从根引用(静态变量、CPU寄存器、局部变量或参数实例)查找所有可达对象,然后移动可达对象紧挨着放在一起,最后释放所有不可达对象所占用的内存。
在一次垃圾回收过程中,并不会清除所有未引用对象。根据对象的生存期统计,相比于长期存在的对象,最近创建的对象更可能需要被垃圾回收。因此,.NET垃圾回收器支持“代”(generation)的概念。堆被分为3代,新创建的对象被放在第0代堆上,这部分会以较高的频率执行垃圾回收,在一次垃圾回收后,“存活”下来的对象会被移动到下一代,最高移动到第二代。越高的代执行垃圾回收的频率越低。
可以调用
System.GC.Collect()
方法强制启动一个垃圾回收过程,但一般不需要使用。如果调用无参数System.GC.Collect()
,则会对所有”代“执行垃圾回收。
资源释放
垃圾回收能自动释放托管对象的内存,但并不会释放非托管资源(例如数据库连接、文件句柄、网络连接等)。
当托管类在封装对非托管资源的直接或间接引用时,需要用下文的方法确保非托管资源的释放。
析构函数
C#的析构函数的语法与C++类似,带有~前缀,之后与类名相同,没有返回类型,不带参数,没有访问修饰符。示例:
class MyClass
{
~MyClass()
{
// Finalizer implementation
}
}
C#编译器在编译析构函数时,会隐式地把析构函数的代码编译为重写Finalize()
方法的等价代码。
C#的析构函数也被称为终结器(Finalizer)。
下面代码是编译器编译~MyClass()
生成的IL:
protected override void Finalize()
{
try
{
// Finalizer implementation
}
finally
{
base.Finalize();
}
}
析构函数不能在代码中显式调用,而是在垃圾回收前被自动调用。(如果进程异常终止,终结器将不会运行,例如计算机断电或进程被强制终止。)
没有析构函数的对象在垃圾回收时将被直接释放内存,而具有析构函数的对象在垃圾回收前,先被添加到f-reachable队列,然后在一个独立的线程中执行析构函数,执行完成后从f-reachable队列中移除,然后在下一次垃圾回收释放内存。这将有以下影响:
- 不能确定析构函数什么时候会被执行。
- 将延迟对象从内存中删除的时间,因为f-reachable队列引用了对象,在从f-reachable队列移除前,垃圾回收器不能释放此对象的内存。
- 析构函数中的代码是线程不安全的,需谨慎访问其他托管对象。
- 如果析构函数引发了未处理异常,会难以诊断,所以要避免在析构函数中引发异常。
IDisposable接口
在C#中,推荐使用System.IDisposable
接口代替析构函数释放非托管资源。IDisposable
接口定义了一种模式(具有语言级的支持),该模式为释放非托管的资源提供了确定性的机制,并避免析构函数与垃圾回收相关的问题。
当托管类封装了对非托管资源的间接引用时(也就是已变为托管资源),即托管类封装了具有IDisposable
接口的对象,只需如下简单实现IDisposable
接口:
class MyClass : IDisposable
{
public void Dispose()
{
// implementation
}
}
Dispose()
方法中调用实现了IDisposable
接口的封装对象的Dispose()
方法。
当托管类封装了对非托管资源的直接引用时,需确保调用Dispose()
方法,通过实现析构函数最为一种安全机制,以便在代码中没有调用Dispose()
方法时,由析构函数调用Dispose()
方法,示例:
class MyClass : IDisposable
{
private bool _disposedValue;
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// TODO: 释放托管状态(托管对象)
}
// TODO: 释放未托管的资源(未托管的对象)并重写终结器
// TODO: 将大型字段设置为 null
_disposedValue = true;
}
}
// TODO: 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才定义终结器
~MyClass()
{
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
Dispose(disposing: false);
}
public void Dispose()
{
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
上述代码中:
-
Dispose(bool disposing)
方法中真正完成清理工作,其参数用于区分是由Dispose()
方法调用,还是由析构函数调用,其原因是:- 如果使用者调用
Dispose()
,应清理所有与该对象相关的资源,包括托管和非托管的资源。 - 如果调用了析构函数,原则上所有的资源仍需要清理,但是析构函数由垃圾回收器调用,不应访问其他托管的对象,因为此时是线程不安全的,不能确定其他托管对象的状态。这种情况下,最好只清理非托管资源,引用的任何托管对象由它们自己的析构函数执行清理过程。
- 如果使用者调用
-
_disposedValue
成员变量表示对象是否已被清理,确保不试图多次清理资源。它也可以用于在执行实例方法之前检查对象是否已清理。 -
Dispose()
方法包含对System.GC.SuppressFinalize()
方法的调用。SuppressFinalize()
方法告诉垃圾回收器不需要调用这个对象的析构函数了,即等同于这个对象没有析构函数,因为Dispose()
方法已经完成了所需的清理工作,所以析构函数不需要做任何工作。这样就避免了析构函数导致的对象延迟释放。
使用using
语句调用Dispose()
方法
使用Dispose()
方法的简单用法如下:
var myClass = new MyClass();
// do your processing
myClass.Dispose();
如果中间出现了异常代码将不会运行到Dispose()
方法,所以应使用try/finally
块,更简单的方法是使用using
语句,这等同于try/finally
块,将确保在对象离开作用域时自动调用Dispose()
方法:
using(var myClass = new MyClass())
{
// do your processing
}
.NET的一些类如果要关闭资源(如文件或数据库),就有
Close()
和Dispose()
方法,其Close()
方法只是调用了Dispose()
,新的类只实现了Dispose()
方法,因为这个模式已经被大家所熟悉。
IDisposable接口和析构函数的规范
- 如果类定义了实现
IDisposable
的成员,该类也应该实现IDisposable。 - 实现
IDisposable
并不意味着也要实现析构函数,析构函数会带来额外的开销。只有包含非托管资源时才应该实现析构函数。 - 如果实现了析构函数,就应该实现
IDisposable
接口,以便非托管资源可以确定性释放,而不用等到垃圾回收时才释放。 - 析构函数的执行顺序是没有保证的,不能在析构函数中访问其他托管对象,它们的析构函数可能会先执行。
- 要确保
Dispose()
方法可以重入(可被多次调用)。
来源链接:https://www.cnblogs.com/yada/p/18844288
如有侵犯您的版权,请及时联系3500663466#qq.com(#换@),我们将第一时间删除本站数据。
暂无评论内容