You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# API内存泄漏排查求助:请求后内存未释放

C# API 请求后内存持续增长,无法定位泄漏点

我的C# API处理简单请求时,会堆积byte[]、字符串等不必要的数据,每次请求后内存占用增加数KB且从未释放。请求50次后,内存占用增长数MB,即使手动触发GC也无法释放这些内存,已确认存在内存泄漏但找不到泄漏点。

功能说明

该接口通过BearerToken识别用户,返回该用户的所有笔记本数据。

涉及代码

GetCurrentUser(静态方法)

public static async Task<UserModel?> GetCurrentUser(DataBaseContext _db, string bearerToken)
{
    //get from the bearer token the username
    //create jwt token
    var token = new JwtSecurityToken(jwtEncodedString: bearerToken);

    //get the expiry from the token
    var userName = token.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;

    //get the whole user form the db
    var user = _db.User
        .Include(x => x.Invoices)
        .Include(x => x.Notebook)
        .Include(x => x.Logins)
        .FirstOrDefaultAsync(x => x.Username == userName);

    return await user;
}

GetCurrentUser(私有方法)& GetAll 接口

private async Task<UserModel?> GetCurrentUser()
{
    // Get the bearer token from the header
    var bearerToken = HttpContext.Request.Headers["bearerToken"].ToString();
    var user = await UserCore.GetCurrentUser(_db, bearerToken);
    return user;
}

[HttpGet]
[Route("GetAll")]
public async Task<IActionResult> GetAll()
{
    //set the users traceId
    Guid traceId = Guid.NewGuid();
            
    //get the user
    var user = await GetCurrentUser();
    if (user == null)
        return new BadRequestObjectResult(ResponseMgr.CreateResponse(400, traceId, "The user does not exist"));

    //get all notes
    var notes = user.Notebook.ToList();
    
    //return all notes
    return new OkObjectResult(ResponseMgr.CreateResponse(200, traceId, "All notes", new Dictionary<string, object> { { "notes", notes } }));
}

堆内存分析

堆图显示存在重复的字符串列表,其中包含某笔记本的表情符号内容,请求结束后这些数据未被释放。堆图列依次为「value」「wasted」「count」,底部为「total」。

排查建议

  • 检查DbContext生命周期:确认DataBaseContext是否注册为Scoped生命周期。如果是Singleton,EF会缓存所有加载的实体,导致内存无法释放,这是此类问题的常见原因。
  • 移除不必要的Include:当前GetCurrentUser中加载了Invoices、Logins,但接口仅需Notebook数据,多余的关联数据会被上下文持有,增加内存占用。只保留Include(x => x.Notebook)即可。
  • 排查ResponseMgr.CreateResponse实现:如果该方法使用静态集合缓存响应数据,或持有对象的强引用,会导致数据无法被GC回收。检查是否存在静态变量、单例缓存等逻辑。
  • 确认DbContext释放逻辑:ASP.NET Core中Scoped DbContext会在请求结束自动释放,但如果有额外代码持有DbContext的引用(比如静态变量、长期存活的对象),会导致关联的实体无法被回收。
  • 检查JwtSecurityToken相关缓存:创建JwtSecurityToken时,是否有第三方库或自定义代码缓存了token或其Claims?静态缓存会导致字符串(如用户名)被长期持有。
  • 用内存分析工具追踪引用链:使用Visual Studio内存探查器或dotMemory,找到未释放字符串的根引用,查看是被DbContext、静态变量还是其他长期对象持有,精准定位泄漏点。

内容的提问来源于stack exchange,提问作者Schecher_1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 21:55:01