MVC ASP.NET正在使用大量内存

Kev*_*Lee 10 c# memory asp.net asp.net-mvc entity-framework

如果我只浏览应用程序上的某些页面,它大约为500MB.这些页面中的许多页面访问数据库,但此时,我只有大约几行用于10个表,大多数存储字符串和一些小于50KB的小图标.

当我下载文件时,会出现真正的问题.该文件大约为140MB,并存储为varbinary(MAX)数据库中的文件.内存使用量突然升至1.3GB,瞬间降至1GB.该操作的代码如下:

public ActionResult DownloadIpa(int buildId)
{
    var build = _unitOfWork.Repository<Build>().GetById(buildId);
    var buildFiles = _unitOfWork.Repository<BuildFiles>().GetById(buildId);
    if (buildFiles == null)
    {
        throw new HttpException(404, "Item not found");
    }

    var app = _unitOfWork.Repository<App>().GetById(build.AppId);
    var fileName = app.Name + ".ipa";

    app.Downloads++;
    _unitOfWork.Repository<App>().Update(app);
    _unitOfWork.Save();

    return DownloadFile(buildFiles.Ipa, fileName);
}

private ActionResult DownloadFile(byte[] file, string fileName, string type = "application/octet-stream")
{
    if (file == null)
    {
        throw new HttpException(500, "Empty file");
    }

    if (fileName.Equals(""))
    {
        throw new HttpException(500, "No name");
    }

    return File(file, type, fileName);            
}
Run Code Online (Sandbox Code Playgroud)

在我的本地计算机上,如果我什么都不做,内存使用量将保持在1GB.如果我然后返回导航到某些页面,它会回落到500MB.

在部署服务器上,无论我做什么,它在第一次下载后都会保持在1.6GB.我可以通过不断下载文件来增加内存使用量,直到它达到3GB,然后下降到1.6GB.

在每个控制器中,我都覆盖了这个Dispose()方法:

protected override void Dispose(bool disposing)
{
    _unitOfWork.Dispose();
    base.Dispose(disposing);
}
Run Code Online (Sandbox Code Playgroud)

这指的是:

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

public void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            _context.Dispose();
        }
    }

    _disposed = true;
}
Run Code Online (Sandbox Code Playgroud)

因此,每次处理控制器时都应该处理我的工作单元.我使用的是Unity,我使用Heirarchical Lifetime Manager注册了工作单元.

以下是Profiler的一些屏幕截图:

在此输入图像描述

在此输入图像描述

在此输入图像描述

我相信这可能是问题,或者我走错了路.为什么要Find()使用300MB?

编辑:

库:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    internal IDbContext Context;
    internal IDbSet<TEntity> DbSet;

    public Repository(IDbContext context)
    {
        Context = context;
        DbSet = Context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> GetAll()
    {            
        return DbSet.ToList();
    }

    public virtual TEntity GetById(object id)
    {
        return DbSet.Find(id);
    }

    public TEntity GetSingle(Expression<Func<TEntity, bool>> predicate)
    {
        return DbSet.Where(predicate).SingleOrDefault();
    }

    public virtual RepositoryQuery<TEntity> Query()
    {
        return new RepositoryQuery<TEntity>(this);
    }

    internal IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        List<Expression<Func<TEntity, object>>> includeProperties = null)
    {
        IQueryable<TEntity> query = DbSet;

        if (includeProperties != null)
        {
            includeProperties.ForEach(i => query.Include(i));
        }

        if (filter != null)
        {
            query = query.Where(filter);
        }

        if (orderBy != null)
        {
            query = orderBy(query);
        }

        return query.ToList();
    }

    public virtual void Insert(TEntity entity)
    {
        DbSet.Add(entity);
    }

    public virtual void Update(TEntity entity)
    {
        DbSet.Attach(entity);
        Context.Entry(entity).State = EntityState.Modified;
    }

    public virtual void Delete(object id)
    {
        var entity = DbSet.Find(id);

        Delete(entity);
    }

    public virtual void Delete(TEntity entity)
    {
        if (Context.Entry(entity).State == EntityState.Detached)
        {
            DbSet.Attach(entity);
        }

        DbSet.Remove(entity);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑2:

我为各种场景运行了dotMemory,这就是我得到的.

在此输入图像描述

红色圆圈表示有时在一次访问时会发生多次上升和下降.蓝色圆圈表示下载40MB文件.绿色圆圈表示下载140MB文件.此外,在很多时候,即使页面立即加载,内存使用量也会持续增加几秒钟.

Mar*_*ijn 9

因为文件很大,所以它是在大对象堆上分配的,它是用gen2集合收集的(你在配置文件中看到,紫色块是大对象堆,你看到它在10秒后收集).

在生产服务器上,您的内存可能比本地计算机上的内存多得多.因为内存压力较小,所以集合不会频繁出现,这就解释了为什么它会增加更多的数字 - 在收集之前LOH上有几个文件.

如果在MVC和EF中的不同缓冲区中,一些数据也会在不安全的块中被复制,那么我不会感到惊讶,这解释了非托管内存增长(EF的薄尖峰,MVC的广阔平台)

最后,一个500MB的基线是一个大项目并不完全令人惊讶(疯狂!但是真的!)

所以回答你的问题为什么它使用如此多的内存非常可能是"因为它可以",或者换句话说,因为没有内存压力来执行gen2集合,并且下载的文件在大对象中未被使用堆直到集合驱逐它们,因为生产服务器上的内存很丰富.

这可能不是一个真正的问题:如果有更多的内存压力,会有更多的收集,你会看到更低的内存使用率.

至于如何应对,我担心你对实体框架不满意.据我所知,它没有流API.WebAPI确实允许按顺序传输响应,但是如果整个大对象都在内存中,这对你没有多大帮助(尽管它可能有助于某些人在MVC未经探索的部分中使用非托管内存) .


usr*_*usr 4

将 GC.Collect() 添加到 Dispose 方法中以进行测试。如果泄漏持续存在,则为真正的泄漏。如果它消失了,那只是 GC 延迟了。

你这样做了并说道:

@usr 内存使用量现在几乎没有达到 600MB。那么真的只是推迟了吗?

显然,如果 GC.Collect 删除了您担心的内存,则不会发生内存泄漏。如果您想真正确定,请运行测试 10 次。内存使用应该稳定。

当文件在不同的组件和框架中传输时,以单个块的形式处理如此大的文件可能会导致内存使用量成倍增加。切换到流式传输方法可能是个好主意。