一、何为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: 将大型字段设置为 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关键词才能完成生命周期的托管 ]