赞
踩
垃圾回收是运行时的核心功能,旨在回收不再被引用的对象所占用的内存,并不会处理如数据库连接、句柄(文件、窗口等)、网络端口及硬件设备。此外,垃圾回收器根据是否存在引用来决定要清理什么,处理的是引用对象,并且只回收堆上的内存。如果维持一个对象的引用,就会阻止垃圾回收器回收对象所占用的内存。
.Net垃圾回收器采用的是mark-and-compact算法(首先确定所有可达对象,然后移动这些对象,使它们紧挨着存放,有点类似于磁盘碎片整理)。一次垃圾回收周期开始时,垃圾回收器会识别出对象的所有根引用,然后遍历根引用所标识的树形结构,并递归确定所有根引用指向的对象,这样,垃圾回收器就识别出了所有可达对象。执行垃圾回收时,垃圾回收器将所有可达对象紧挨着放在一起,从而覆盖不可达对象所占用的内存。
为了定位和移动所有的可达对象,系统要在垃圾回收器运行期间维持状态的一致性。因此进程中所有托管线程会在垃圾回收期间暂停。
并非所有的垃圾都一定会在一个垃圾回收周期中清除,相较于长期存在的对象,最近创建的对象更有可能需要垃圾回收。因此.Net支持“代”的概念:
前面提到的“不再被引用的对象”指的是强引用,强引用维持着对象的可访问性,阻止垃圾回收器清除对象所占用的内存。与强引用相对的还有弱引用,弱引用不会阻止对象进行垃圾回收,但会维持一个引用,这样,对象在被垃圾回收器清除之前可以重用。
弱引用是为了创建起来开销很大,维护开销也特别大的引用对象而设计的。 弱引用相当于对象的一个内存缓存,当再次加载弱引用的对象时,如果这个对象还没有被回收,就可以被快速获取到,但假如已经被垃圾回收器回收,就还是要重新创建它们。
弱引用的用法如下:
public static class WeakReferenceExample { // 弱引用 private static WeakReference<byte[]>? Data { get; set; } /** * 获取数据 */ public static byte[] GetData() { // 局部变量的作用是维持加载的数据的引用,防止被回收 byte[]? target; // 如果Data为空,加载数据并创建一个弱引用指向该数据 if (Data is null) { target = LoadData(); Data = new WeakReference<byte[]>(target); return target; } // 如果Data不为空,说明一定有一个WeakReference对象 // 直接调用TryGetTarget方法提取数据 else if (Data.TryGetTarget(out target)) { return target; } // 如果无法提取数据,则重新加载 else { target = LoadData(); Data.SetTarget(target); return target; } } /** * 加载数据 */ private static byte[] LoadData() { return new byte[1000]; } }
垃圾回收旨在提高内存利用率,而非清理文件句柄、数据库连接、端口或其他有限资源。 而终结器则允许程序员写代码来清理这类资源。
终结器不能在代码中显式调用,是垃圾回收器负责为对象实例调用终结器。因此,开发者无法确定终结器的执行时间,唯一确定的是终结器会在对象最后一次使用之后,程序正常关闭之前的某个时间运行。 但在计算机关机、程序强行终止等情况下,终结器也有可能不被调用。(在.Net Core中,正常情况下,终结器在程序结束前也可能不被调用)。
终结器的使用方法如下:
public class TerminatorExample { public FileStream? Stream { get; set; } public FileInfo? File { get; set; } /** * 终结器 */ ~TerminatorExample() { try { Close(); } catch (Exception e) { Console.WriteLine(e); } } /** * 关闭资源 */ public void Close() { Stream?.Dispose(); try { File?.Delete(); } catch (IOException e) { Console.WriteLine(e); } } }
注意:终结器不允许传递任何参数,因此也不能重载;终结器只能垃圾回收器调用,因而添加访问修饰符没有意义;基类中的终结器作为对象终结调用的一部分会被自动调用。
对于执行终结器来说,另一个可能的选择是调用System.GC.WaitForPendingFinalizers()
。它将使当前线程暂停,当所有被垃圾回收的对象的终结器调用完成后再恢复运行。
终结器是对资源进行清理的备用机制,开发者更应该提供确定性终结的方法。C#为确定性终结这个使用模式提供了接口。只需要实现IDisposable接口的Dispose()
方法即可。
public class IDisposableExample : IDisposable
{
public void Dispose()
{
// 清理资源
}
}
在Dispose()
方法中最好包含对System.GC.SuppressFinalize(this)
方法的调用。它的作用是从终结队列中移除当前类实例,因为所有的资源清理都在DIspose()中完成了,不需要再等待终结器执行。如果不调用这个方法,对象的实例就会被包含到终结队列中,而只有它的终结方法被调用,垃圾回收器才会对其进行垃圾回收。然而垃圾回收器不会主动调用终结方法,它是由一个额外的线程根据执行上下文,挑选合适的时间进行处理。这就造成了垃圾回收的延迟。
如果我们希望在需要时才创建对象,而不是提前创建对象,可以使用推迟初始化的模式。
public class DefInitExample
{
public TerminatorExample Terminator =>
InternalTerminator ?? (InternalTerminator = new TerminatorExample());
private TerminatorExample? InternalTerminator { get; set; } = null;
}
在Terminator属性的表达式中,直接返回InternalTerminator属性,在返回之前先校验InternalTerminator是否为空,如果空,就先将其实例化。这样就只有在调用Terminator属性的get访问器时才会实例化,否则永远不会实例化。
从C#4.0开始,我们可以使用System.Lazy<T>
实现上面的功能。
public class DefInitExample2
{
public TerminatorExample Terminator => InternalTerminator.Value;
private Lazy<TerminatorExample> InternalTerminator { get;}
= new Lazy<TerminatorExample>(()=>new TerminatorExample());
}
两种方法唯一的区别在于System.Lazy<T>
是线程安全的。
参考文献:
[1]马克·米凯利斯.C#8.0本质论[M].机械工业出版社.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。