当前位置:   article > 正文

C#中的垃圾回收、资源清理及推迟初始化_c# 清理解决方案

c# 清理解决方案

1.垃圾回收

垃圾回收是运行时的核心功能,旨在回收不再被引用的对象所占用的内存,并不会处理如数据库连接、句柄(文件、窗口等)、网络端口及硬件设备。此外,垃圾回收器根据是否存在引用来决定要清理什么,处理的是引用对象,并且只回收堆上的内存。如果维持一个对象的引用,就会阻止垃圾回收器回收对象所占用的内存。

.Net垃圾回收器采用的是mark-and-compact算法(首先确定所有可达对象,然后移动这些对象,使它们紧挨着存放,有点类似于磁盘碎片整理)。一次垃圾回收周期开始时,垃圾回收器会识别出对象的所有根引用,然后遍历根引用所标识的树形结构,并递归确定所有根引用指向的对象,这样,垃圾回收器就识别出了所有可达对象。执行垃圾回收时,垃圾回收器将所有可达对象紧挨着放在一起,从而覆盖不可达对象所占用的内存。

为了定位和移动所有的可达对象,系统要在垃圾回收器运行期间维持状态的一致性。因此进程中所有托管线程会在垃圾回收期间暂停。

并非所有的垃圾都一定会在一个垃圾回收周期中清除,相较于长期存在的对象,最近创建的对象更有可能需要垃圾回收。因此.Net支持“代”的概念:

  • 第0代: 这是最年轻的代,其中包含短生存期对象,如临时变量。垃圾回收最常发生在此代中。
  • 第1代: 这一代包含短生存期对象并用作短生存期对象和长生存期对象之间的缓冲区。在第0代未被回收的对象会升级到第1代,因为未被回收的对象往往具有较长的生存期。
  • 第2代: 这一代包含长生存期对象,如服务器应用中处于活动状态的静态数据对象。第1代未被回收的对象会升级至第2代,如果在第2代还未被回收,会继续留在第2代托管堆中,直到在将来的回收中确定它们无法访问为止。此外,大型对象堆上的对象也在第2代中收集。

2.弱引用

前面提到的“不再被引用的对象”指的是强引用,强引用维持着对象的可访问性,阻止垃圾回收器清除对象所占用的内存。与强引用相对的还有弱引用,弱引用不会阻止对象进行垃圾回收,但会维持一个引用,这样,对象在被垃圾回收器清除之前可以重用。

弱引用是为了创建起来开销很大,维护开销也特别大的引用对象而设计的。 弱引用相当于对象的一个内存缓存,当再次加载弱引用的对象时,如果这个对象还没有被回收,就可以被快速获取到,但假如已经被垃圾回收器回收,就还是要重新创建它们。

弱引用的用法如下:

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];
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

3.终结器

垃圾回收旨在提高内存利用率,而非清理文件句柄、数据库连接、端口或其他有限资源。终结器则允许程序员写代码来清理这类资源。

终结器不能在代码中显式调用,是垃圾回收器负责为对象实例调用终结器。因此,开发者无法确定终结器的执行时间,唯一确定的是终结器会在对象最后一次使用之后,程序正常关闭之前的某个时间运行。 但在计算机关机、程序强行终止等情况下,终结器也有可能不被调用。(在.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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

注意:终结器不允许传递任何参数,因此也不能重载;终结器只能垃圾回收器调用,因而添加访问修饰符没有意义;基类中的终结器作为对象终结调用的一部分会被自动调用。

对于执行终结器来说,另一个可能的选择是调用System.GC.WaitForPendingFinalizers()。它将使当前线程暂停,当所有被垃圾回收的对象的终结器调用完成后再恢复运行。

确定性终结

终结器是对资源进行清理的备用机制,开发者更应该提供确定性终结的方法。C#为确定性终结这个使用模式提供了接口。只需要实现IDisposable接口的Dispose()方法即可。

public class IDisposableExample : IDisposable
{
    public void Dispose()
    {
        // 清理资源
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Dispose()方法中最好包含对System.GC.SuppressFinalize(this)方法的调用。它的作用是从终结队列中移除当前类实例,因为所有的资源清理都在DIspose()中完成了,不需要再等待终结器执行。如果不调用这个方法,对象的实例就会被包含到终结队列中,而只有它的终结方法被调用,垃圾回收器才会对其进行垃圾回收。然而垃圾回收器不会主动调用终结方法,它是由一个额外的线程根据执行上下文,挑选合适的时间进行处理。这就造成了垃圾回收的延迟。

4.推迟初始化

如果我们希望在需要时才创建对象,而不是提前创建对象,可以使用推迟初始化的模式。

public class DefInitExample
{
    public TerminatorExample Terminator => 
        InternalTerminator ?? (InternalTerminator = new TerminatorExample());

    private TerminatorExample? InternalTerminator { get; set; } = null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在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());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

两种方法唯一的区别在于System.Lazy<T>是线程安全的。

参考文献:
[1]马克·米凯利斯.C#8.0本质论[M].机械工业出版社.

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/540940
推荐阅读
  

闽ICP备14008679号