垃圾回收 (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 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 和生存期管理
  • 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;
        }

垃圾回收 (GC) 在 .NET Core 中是如何工作的?(二)_第1张图片

可以使用 ArrayPool 创建 byte 缓冲区池,从而优化上面的代码。 静态实例可在请求间重用。

此方法的不同之处在于,会从 API 返回共用对象。 也就是说:

  • 从方法返回后,对象会立即脱离控制。
  • 无法释放对象。

若要设置对象的释放,请执行以下操作:

  • 将共用数组封装在可释放对象中。
  • 向 HttpContext.Response.RegisterForDispose 注册共用对象。

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;
}

垃圾回收 (GC) 在 .NET Core 中是如何工作的?(二)_第2张图片

主要差异是分配的字节数,因而第 0 代回收数要少得多。

你可能感兴趣的:(.netcore,java,算法)