一、何为GC
数据是存储在内存中的,而内存又分为Stack栈内存和Heap堆内存
Stack栈内存 | Heap堆内存 |
---|---|
速度快、效率高 | 结构复杂 |
类型、大小有限制 | 对象 |
只能保存简单的数据 | 引用数据类型 |
基础数据类型、值类型 | - |
举个例子
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或派生类包装任何非托管句柄,可以免去编写终结器的过程。
若无必要,不要使用
使用终结器会造成性能的损失。
代码举例
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方法外面,因此看不到析构方法的输出。
因此要看到输出,就要降低对象的作用域。
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实现资源的释放
namespace System
{//释放外部资源public interface IDisposable{void Disposable();}
}
典型案例
public class Custom : Disposable
{void Method();void Dispose();
}static main()
{using (var obj = new Customs()){obj.Method();}
}
使用Dispose方法就必须使用using关键字
五、使用IDisposable回收非托管资源
首先在nuget工具中下载安装SqlClient
示例代码
Program.cs
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
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
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
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: 将大型字段设置为 nulldisposedValue = 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关键词才能完成生命周期的托管 ]