【问题标题】:Why ASP Net Core 2.2 do not release memory?为什么 ASP Net Core 2.2 不释放内存?
【发布时间】:2022-05-11 08:22:39
【问题描述】:

我正在使用默认项目测试 ASP Net Core 2.2:

Visual Studio > 文件 > 新建 > 项目 > ASP NET Core Web 应用程序 > 下一步 > 创建。 按界面上的IIS Express按钮,自动转到https://localhost:44356/api/values

编辑:重点是,我正在测试 ASP Net Core,以防人们暴力破解垃圾邮件。

我只修改了这个:

        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

编辑:为此,我编辑了我的代码以进行合理的测试,产生最小的可重现示例

// GET api/values

[HttpGet]
public ActionResult<IEnumerable<Post>> Get()
{
    // Testing for brute force
    return Enumerable.Range(0, 100).Select(x => "Example Value").ToList();
}

起始进程内存为 80 MB。

但是,当我尝试刷新页面时,开始内存不断出现:

编辑:原来这个问题从 2 年前就已经存在,没有解决方案,并且有 真实生活示例体验https://github.com/aspnet/AspNetCore/issues/1976

我很确定黄色箭头表示 GC 工作正常,但我的进程内存使用量并不是每个请求都下降。 我不确定这是否是 ASP Net Core 的功能或错误,我预计它会在请求后下降 / 每次用户获取 /values 时不会增加

编辑:我不希望人们危险地向我的应用程序发送垃圾邮件,并且由于内存泄漏而开始崩溃。如果这个解决方案还没有修复,我害怕为我的公司使用 Net Core。

为什么 ASP Net Core 2.2 不释放内存?

我试过在两台不同的笔记本电脑上测试,结果是一样的。

编辑:我的同事也有这个问题。

编辑:我试过用dot net core v2.1,结果是一样的。

我已经尝试过 GCCollect();结果是一样的。

旧图参考

Starting Process Memory

Process Memory keeps coming up

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • 设计? GC 收集的运行时通常会保留内存,除非内存不足

标签: c# asp.net-core asp.net-web-api memory-leaks


【解决方案1】:

ASP.NET Core 使用的内存似乎比应有的多,因为它默认配置为使用服务器 GC(与 Workstation GC 相对)。有关类似问题的讨论,请参阅here。理论上,当您的服务器面临内存压力时,应用程序应该能够减少其内存占用。

this MSDN docGithub CoreCLR doc 中有关此主题的更多信息。

要查看是否确实是服务器 GC 导致了额外的内存消耗,您可以在 csproj 文件中将 GC 策略设置为工作站:

<PropertyGroup> 
    <ServerGarbageCollection>false</ServerGarbageCollection>
</PropertyGroup>

【讨论】:

  • 感谢您的回答!真的很有帮助!
【解决方案2】:

!!!最后编辑:这个解决方案不是最好的方法,有些人可能会说这是一个糟糕的解决方案......我推荐下面的答案!!!

我在同样的问题上苦苦挣扎。经过一番研究,我发现 .NET Core 会尽可能分配新的内存空间。必要时会发布,但是这个发布比其他发布更需要资源,因为你必须这样使用GC:

GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();

我的解决方案是创建一个中间件:

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace RegisterValidator
{
    public class GCMiddleware
    {
        private readonly RequestDelegate _next;

        public GCMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            await _next(httpContext);
            GC.Collect(2, GCCollectionMode.Forced, true);
            GC.WaitForPendingFinalizers();
        }
    }
}

编辑:这个中间件会在每次调用后提供一个GC.Collect

并在Startup.csConfigure()方法中注册:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider service, ILoggerFactory loggerFactory)
    {

        app.UseMiddleware<GCMiddleware>();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseStaticFiles();
        //app.UseHttpsRedirection();
        app.UseMvcWithDefaultRoute();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("\"No ");

        });
    }

也许还有其他解决方案,但我还没有找到。希望对你有帮助。

编辑:如果您不确定内存使用情况是因为存在大量其他问题的分配空间,您可以使用dotMemory,它将更详细地分析您的内存使用情况比你的 Visual Studio 更详细。

【讨论】:

  • 编辑:没关系,在使用中间件进行更多测试后,进程内存的增长率低于没有中间件的情况,这对我现在的解决方案非常有用,尽管我不知道副作用调用GC。
  • @Kevin 我很高兴它有帮助。从理论上讲,这种内存分配正在提高性能,因为在内存使用率很高的情况下,程序不需要向操作系统请求内存空间。我在中间件中使用的 GC 的第二代清理比低代有更多的计算需求。这就是为什么 .NET 不会每次在 VS 的内存监视窗口中看到 CG 活动时都执行此操作。也许最好的解决方案是以某种方式衡量使用和不使用中间件的性能差异。
  • 这是一个糟糕的解决方案。不要使用它。
  • @PurnimaNaik 因为在真正的应用程序中,您永远不应该显式调用 GC.Collect()。你可以在互联网上阅读它。那是大罪。同样适用于 Java。如果您将在每个请求中调用 collect,那么拥有托管内存有什么意义。
  • 对每个请求强制收集对性能来说很糟糕
【解决方案3】:

TL;DR;只需确保您的 DBContext 不是瞬态的。

我尝试了两个答案,但在我的情况下,问题完全不同。在项目开始时,其中一位开发人员进行了一些实验,使应用程序上下文的生命周期是短暂的。上下文变量的默认生命周期是有范围的,它应该是这样的,至少在我们的例子中是这样。

services.AddDbContext&lt;AppContext&gt;(ServiceLifetime.Transient);

要将上下文保持在范围内,您可以通过以下两种方式注入它:

  • services.AddDbContext&lt;AppContext&gt;();
  • services.AddDbContext&lt;AppContext&gt;(ServiceLifetime.Scoped);

简要说明: 就像 API 中的任何其他服务一样,DBContext 也可以注入生命周期为

  1. 瞬态 - 为每个存储库调用创建新的 DBContext 实例
  2. Scoped - 为一次 API 调用创建一个 DBContext 实例
  3. 单例 - 为所有 API 调用创建一次 DBContext 实例

因此,如果您使 DBContext Transient 并且您的一个 API 调用从 10 个不同的存储库中获取数据。这将创建 10 个不同的 DBContext 实例。内存消耗会很快,GC没有足够的时间释放内存。这可能会使网站崩溃 不到一小时,只有 2-3 个用户。

【讨论】:

    【解决方案4】:

    我发现解决方案对我有用

    编辑 runtimeconfig.json 文件,并设置以下值:

    {
       "runtimeOptions": {
          "configProperties": {
             "System.GC.HeapHardLimitPercent": 1
          }
       }
    }
    

    HeapHardLimitPercent 值设置得越低,GC 的工作就越积极。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-12
      • 2014-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-02
      相关资源
      最近更新 更多