垃圾回收 (GC) 在 .NET Core 中是如何工作的?(二)
接上一篇文章垃圾回收 (GC) 在 .NET Core 中是如何工作的?-CSDN博客
GC 会分配堆段,其中每个段都是一系列连续的内存。 置于堆中的对象归类为 3 个代系之一:0、1 或 2。 代系可确定 GC 尝试在应用不再引用的托管对象上释放内存的频率。 编号较低的代系会更加频繁地进行 GC。
对象会基于其生存期从一个代系移到另一个代系。 随着对象生存期延长,它们会移到较高代系。 如前所述,较高代系进行 GC 的频率较低。 短期生存的对象始终保留在第 0 代中。 例如,在 Web 请求存在期间引用的对象的生存期较短。 应用程序级别单一实例通常会迁移到第 2 代。
当 ASP.NET Core 应用启动时,GC 会:
为初始堆段保留一些内存。
在运行时加载时提交一小部分内存。
HttpClient
未正确使用?HttpClient?可能会导致资源泄漏。 系统资源(如数据库连接、套接字、文件句柄等):
- 比内存更短缺。
- 在泄漏时出现的问题比内存更多。
经验丰富的 .NET 开发人员会对实现?IDisposable?的对象调用?Dispose。 未释放实现?IDisposable
?的对象通常会导致内存泄漏或系统资源泄漏。
HttpClient
?实现了?IDisposable
,但是不应在每次调用时进行释放。 而是应重用?HttpClient
。
下面的api会对每个请求创建并释放新的?HttpClient
?实例:
[HttpGet("httpclient1")]
public async Task<int> 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<int> 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;
}
可以使用?ArrayPool<T>?创建?byte
?缓冲区池,从而优化上面的代码。 静态实例可在请求间重用。
此方法的不同之处在于,会从 API 返回共用对象。 也就是说:
- 从方法返回后,对象会立即脱离控制。
- 无法释放对象。
若要设置对象的释放,请执行以下操作:
- 将共用数组封装在可释放对象中。
- 向?HttpContext.Response.RegisterForDispose?注册共用对象。
RegisterForDispose
?会负责对目标对象调用?Dispose
,以便仅当 HTTP 请求完成时才会将其释放
private static ArrayPool<byte> _arrayPool = ArrayPool<byte>.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 代回收数要少得多。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!