C#学习(十四)——垃圾回收、析构与IDisposable

一、何为GC

数据是存储在内存中的,而内存又分为Stack栈内存和Heap堆内存

Stack栈内存 Heap堆内存
速度快、效率高 结构复杂
类型、大小有限制 对象
只能保存简单的数据 引用数据类型
基础数据类型、值类型 -

举个例子

csharp 复制代码
var c= new Customer{
	id: 123,
	name: "Jack"
	address: "珠海"
}

在堆内存中就保存了信息

#1000
123 Jack 珠海

而在栈内存中仅保存了需要调用的地址c* = 1000------reference

当删除时,需要先删除堆内存的数据,再删除栈内存的数据,然而如果先删除了栈内存的数据,那么对内存中的数据就再也无法找到,也无法删除,无法重复利用,就会造成内存泄漏。

因此,为了便捷,如JAVA、C#等语言引入了垃圾回收机制,使得程序员只需要关注于对象本身即可。

如果一段对象的引用数量为0,则代表对象的声明周期结束。

二、GC是如何工作的

运行垃圾回收的成本很高,需要不断地遍历所有数据,因此使用了复杂的机制来解决高效运行问题。------Generations分代回收

将数据对象分成三组

G0 G1 G2
暂时性的对象 中长期对象 长期对象
每次运行GC都检查 检查频率下降 GC偶尔来检查

内存不足时,GC会强行清理所有对象

GC不止处理垃圾清理

  • [ 标记、清理堆内存中的死掉的对象]
  • [ 压缩内存、消除间隙,提高对象的创建、读取效率 ]
    不过GC不会处理过大的内存区块

GC独立线程

  • [ GC跑在独立的后台线程中 ]
  • [ 每次运行都需要付出代价,需要消耗计算资源 ]
  • [ 尽可能的减少运行频率、并且尽可能提高运行效率 ]

三、析构方法and终结器

终结器(以前称为析构函数)用于在垃圾回收器收集类实例时执行任何必要的最终清理操作。在大多数情况下,通过使用System.Runtime.InteropServices.SafeHandle或派生类包装任何非托管句柄,可以免去编写终结器的过程。

若无必要,不要使用

使用终结器会造成性能的损失。

代码举例

csharp 复制代码
public class AnywayClass
{
    public AnywayClass()
    {
        Console.WriteLine("AnywayClass类创建");
    }

    ~AnywayClass()
    {
        Console.WriteLine("AnywayClass类销毁");
    }
}
class Program
{
    static void Main(string[] args)
    {
        var anyway = new AnywayClass();

        Console.WriteLine("程序结束");

    }
}

但是运行后会发现,程序并不会输出"AnywayClass类销毁",要判断当前实例是否还会被引用,是根据语句的区域决定的,也就是说,它的作用域是整个main方法,因此垃圾回收是在整个main方法外面,因此看不到析构方法的输出。

因此要看到输出,就要降低对象的作用域。

csharp 复制代码
public class AnywayClass
{
    public AnywayClass()
    {
        Console.WriteLine("AnywayClass类创建");
    }

    ~AnywayClass()
    {
        Console.WriteLine("AnywayClass类销毁");
    }
}

public class SecondClass : AnywayClass
{
    public SecondClass()
    {
        Console.WriteLine("SecondClass创建");
    }
    ~SecondClass()
    {
        Console.WriteLine("SecondClass销毁");
    }
}

public class ThirdClass : SecondClass
{
    public ThirdClass()
    {
        Console.WriteLine("ThirdClass创建");
    }
    ~ThirdClass()
    {
        Console.WriteLine("ThirdClass销毁");
    }
}
class Program
{
    static void DoSomething()
    {
        new ThirdClass();
    }

    static void Main(string[] args)
    {
        DoSomething();
        GC.Collect();//进行垃圾回收
        GC.WaitForPendingFinalizers();//等待所有需要被回收的对象全部被回收
        

        Console.WriteLine("程序结束");

    }
}

  • [ 一个类只能有一个终结器 ]
  • [ 不能继承或重载终结器 ]
  • [ 不能手动调用终结器,只能由垃圾回收器自动调用 ]
  • [ 终结器不使用修饰符或参数 ]

四、Disposable模式

GC不是万能的,GC只能处理托管资源(即那些使用new关键字创建的对象),而无法处理外部资源(比如文件的读取、数据库请求、网络访问等)。

文件读取、网络访问、数据库请求无法托管在.Net平台内部,如果不清理外部资源,将会极大的占用电脑资源,内存不断增长,最后崩溃退出。

因此使用IDisposable实现资源的释放

csharp 复制代码
namespace System
{
	//释放外部资源
	public interface IDisposable
	{
		void Disposable();
	}
}

典型案例

csharp 复制代码
public class Custom : Disposable
{
	void Method();
	void Dispose();
}

static main()
{
	using (var obj = new Customs())
	{
		obj.Method();
	}
}

使用Dispose方法就必须使用using关键字

五、使用IDisposable回收非托管资源

首先在nuget工具中下载安装SqlClient

示例代码

Program.cs

csharp 复制代码
class Program
{
    static void Main(string[] args)
    {
        for(int i = 0; i < 1000; i++)
        {
            var db = new DatabaseHelper();
            var date = db.GetData();
            db.Close();
            Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}; {date}]");
        }

        Console.WriteLine("程序结束");
    }
}

DatabaseHelper.cs

csharp 复制代码
public class DatabaseHelper
{
    private SqlConnection _connection;

    private string _connectionString = $"数据库连接字符串;" +
        $"App = Recycle;" +
        $"Max Pool Size = 100;" +
        $"Pooling = true;";

    public string GetData()
    {
        if(_connection == null)
        {
            _connection = new SqlConnection(_connectionString);
            _connection.Open();
            Console.WriteLine("数据库连接已开启");
        }

        var command = _connection.CreateCommand();
        command.CommandText = "select getdate();";
        return command.ExecuteScalar().ToString();//完成最后输出
    }

    public void Close()
    {
        Console.WriteLine("数据库连接已关闭");
        _connection.Close();
        _connection.Dispose();//注销数据库的连接对象
        _connection = null;
    }
}

实际上,程序员忘记关闭数据库外部资源是一个十分常见的低级错误,为了避免此类错误,可以使用IDisposable接口

Program.cs

csharp 复制代码
class Program
{
    static void Main(string[] args)
    {
        for(int i = 0; i < 1000; i++)
        {
            using (var db = new DatabaseHelper())
            {
                var date = db.GetData();
                Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}; {date}]");
            };                
        }

        Console.WriteLine("程序结束");
    }
}

DatabaseHelper.cs

csharp 复制代码
public class DatabaseHelper : IDisposable
{
    private SqlConnection _connection;

    private string _connectionString = $"Data Source=localhost\\SQLEXPRESS;Initial Catalog=master;Integrated Security=True;Encrypt=True;Trust Server Certificate=True;" +
        $"App = Recycle;" +
        $"Max Pool Size = 100;" +
        $"Pooling = true;";

    public string GetData()
    {
        if(_connection == null)
        {
            _connection = new SqlConnection(_connectionString);
            _connection.Open();
            Console.WriteLine("数据库连接已开启");
        }

        var command = _connection.CreateCommand();
        command.CommandText = "select getdate();";
        return command.ExecuteScalar().ToString();//完成最后输出
    }


    private bool disposedValue;

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // TODO: 释放托管状态(托管对象)
                Console.WriteLine("数据库连接已关闭");
                _connection.Close();
                _connection.Dispose();//注销数据库的连接对象
                _connection = null;
            }

            // TODO: 释放未托管的资源(未托管的对象)并重写终结器
            // TODO: 将大型字段设置为 null
            disposedValue = true;
        }
    }

    // // TODO: 仅当"Dispose(bool disposing)"拥有用于释放未托管资源的代码时才替代终结器
    // ~DatabaseHelper()
    // {
    //     // 不要更改此代码。请将清理代码放入"Dispose(bool disposing)"方法中
    //     Dispose(disposing: false);
    // }

    public void Dispose()
    {
        // 不要更改此代码。请将清理代码放入"Dispose(bool disposing)"方法中
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

注:

  • [ Dispose可以用来回收如数据库连接、文件读取、HTTP长连接等无法托管在.net平台中的外部资源 ]
  • [ 使用Dispose必须实现IDisposable接口 ]
  • [ IDisposable接口需要配合using关键词才能完成生命周期的托管 ]
相关推荐
c4fx7 分钟前
Delphi5利用DLL实现窗体的重用
开发语言·delphi·dll
李小星同志16 分钟前
高级算法设计与分析 学习笔记6 B树
笔记·学习
霜晨月c28 分钟前
MFC 使用细节
笔记·学习·mfc
鸽芷咕30 分钟前
【Python报错已解决】ModuleNotFoundError: No module named ‘paddle‘
开发语言·python·机器学习·bug·paddle
Jhxbdks40 分钟前
C语言中的一些小知识(二)
c语言·开发语言·笔记
java66666888840 分钟前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存41 分钟前
源码分析:LinkedList
java·开发语言
小江湖199441 分钟前
元数据保护者,Caesium压缩不丢重要信息
运维·学习·软件需求·改行学it
代码雕刻家43 分钟前
数据结构-3.1.栈的基本概念
c语言·开发语言·数据结构
Fan_web44 分钟前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html