接上一篇文章垃圾回收 (GC) 在 .NET Core 中是如何工作的?-CSDN博客
GC 会分配堆段,其中每个段都是一系列连续的内存。 置于堆中的对象归类为 3 个代系之一:0、1 或 2。 代系可确定 GC 尝试在应用不再引用的托管对象上释放内存的频率。 编号较低的代系会更加频繁地进行 GC。
对象会基于其生存期从一个代系移到另一个代系。 随着对象生存期延长,它们会移到较高代系。 如前所述,较高代系进行 GC 的频率较低。 短期生存的对象始终保留在第 0 代中。 例如,在 Web 请求存在期间引用的对象的生存期较短。 应用程序级别单一实例通常会迁移到第 2 代。
当 ASP.NET Core 应用启动时,GC 会:
为初始堆段保留一些内存。
在运行时加载时提交一小部分内存。
未正确使用 HttpClient 可能会导致资源泄漏。 系统资源(如数据库连接、套接字、文件句柄等):
经验丰富的 .NET 开发人员会对实现 IDisposable 的对象调用 Dispose。 未释放实现 IDisposable
的对象通常会导致内存泄漏或系统资源泄漏。
HttpClient
实现了 IDisposable
,但是不应在每次调用时进行释放。 而是应重用 HttpClient
。
下面的api会对每个请求创建并释放新的 HttpClient
实例:
[HttpGet("httpclient1")]
public async Task GetHttpClient1(string url)
{
using (var httpClient = new HttpClient())
{
var result = await httpClient.GetAsync(url);
return (int)result.StatusCode;
}
}
上面的代码即使释放了 HttpClient
实例,实际网络连接也需要一些时间才能由操作系统释放。 持续创建新连接时,会发生端口耗尽。 每个客户端连接都需要自己的客户端端口。
防止端口耗尽的一种方法是重用同一个 HttpClient
实例:
private static readonly HttpClient _httpClient = new HttpClient();
[HttpGet("httpclient2")]
public async Task GetHttpClient2(string url)
{
var result = await _httpClient.GetAsync(url);
return (int)result.StatusCode;
}
HttpClient
实例会在应用停止时释放。
可以参阅以下内容,了解处理 HttpClient
实例的生存期:
上面的示例演示了如何将 HttpClient
实例设为静态,并由所有请求重用。 重用可防止资源耗尽。
对象池:
池是预初始化对象的集合,这些对象可以在线程间保留和释放。 池可以定义分配规则,例如限制、预定义大小或增长速率。
下面的 API 会实例化 byte
缓冲区,该缓冲区对每个请求使用随机数字进行填充:
[HttpGet("array/{size}")]
public byte[] GetArray(int size)
{
var random = new Random();
var array = new byte[size];
random.NextBytes(array);
return array;
}
可以使用 ArrayPoolbyte
缓冲区池,从而优化上面的代码。 静态实例可在请求间重用。
此方法的不同之处在于,会从 API 返回共用对象。 也就是说:
若要设置对象的释放,请执行以下操作:
RegisterForDispose
会负责对目标对象调用 Dispose
,以便仅当 HTTP 请求完成时才会将其释放
private static ArrayPool _arrayPool = ArrayPool.Create();
private class PooledArray : IDisposable
{
public byte[] Array { get; private set; }
public PooledArray(int size)
{
Array = _arrayPool.Rent(size);
}
public void Dispose()
{
_arrayPool.Return(Array);
}
}
[HttpGet("pooledarray/{size}")]
public byte[] GetPooledArray(int size)
{
var pooledArray = new PooledArray(size);
var random = new Random();
random.NextBytes(pooledArray.Array);
HttpContext.Response.RegisterForDispose(pooledArray);
return pooledArray.Array;
}
主要差异是分配的字节数,因而第 0 代回收数要少得多。