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

时间:2014-09-23 10:55:02

标签: c# asp.net asp.net-mvc entity-framework memory

如果我只浏览应用程序上的某些页面,它大约为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);            
}

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

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

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

protected override void Dispose(bool disposing)
{
    _unitOfWork.Dispose();
    base.Dispose(disposing);
}

这是指:

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

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

    _disposed = true;
}

因此,每次处理控制器时都应处理我的工作单元。我正在使用Unity,并且我使用Heirarchical Lifetime Manager注册了工作单元。

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

enter image description here

enter image description here

enter image description here

我相信这可能是问题,或者我走错了路。为什么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);
    }
}

编辑2:

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

enter image description here

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

6 个答案:

答案 0 :(得分:9)

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

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

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

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

所以回答你的问题为什么它使用很多很可能的内存是因为它可以&#34;,或者换句话说,因为没有内存压力来执行gen2集合,以及下载的文件在大对象堆中未使用,直到集合清除它们,因为生产服务器上的内存很丰富。

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

至于如何应对,我担心你对实体框架感到不幸。据我所知,它没有流API。 WebAPI确实允许按顺序传输响应,但是如果你将整个大对象放在内存中,这对你来说会有很大的帮助(尽管它可能会帮助一些未经探索的部分中的非托管内存) MVC。

答案 1 :(得分:2)

将GC.Collect()添加到Dispose方法以进行测试。如果泄漏仍然存在,那就是真正的泄漏。如果它消失了,那只是推迟了GC。

你这样做并说:

  

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

显然,如果GC.Collect删除了您担心的内存,则没有内存泄漏。如果你想确定,请运行你的测试10次。内存使用率应该稳定。

在单个块中处理这些大文件会导致内存使用量增加,因为文件会通过不同的组件和框架。切换到流媒体方法可能是个好主意。

答案 2 :(得分:2)

显然,这包括System.Web及其所有孩子占用大约200MB。这被引用为应用程序池的绝对最小值。

我们使用EF 6的Web应用程序,在.Net 4.0中包含220多个实体的模型,启动时空闲时间约为480MB。我们在启动时执行一些AutoMapper操作。内存消耗达到峰值,然后在日常使用中返回到大约500MB。我们已经接受了这个标准。

现在,为您的文件下载峰值。在此问题中探讨了使用ashx处理程序等时Web表单下的问题:ASP.net memory usage during download

我不知道它与MVC中的FileActionResult有什么关系,但你可以看到需要手动控制缓冲区大小以最小化内存峰值。尝试通过以下方式应用该问题答案背后的原则:

Response.BufferOutput = false;
var stream = new MemoryStream(file);
stream.Position = 0;
return new FileStreamResult(stream, type); // Or just pass the "file" parameter as a stream

应用此更改后,内存行为是什么样的?

有关详细信息,请参阅'Debugging memory problems (MSDN)'

答案 3 :(得分:0)

您可能需要以块的形式读取数据并写入输出流。 看看SqlDataReader.GetBytes http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.getbytes(v=vs.110).aspx

答案 4 :(得分:0)

这可能是以下几点之一:

由于您的文件相当大并且存储在您的数据库中并且您是通过Entity Framework获取的,因此您将在一些地方缓存此数据。每个EF请求都会缓存该数据,直到您的上下文被释放为止。从操作返回文件时,数据将再次加载,然后流式传输到客户端。如上所述,所有这些都发生在ASP .NET中。

此问题的解决方案是不使用EFASP .NET直接从数据库中流式传输大型文件。更好的解决方案是使用后台进程将大型文件本地缓存到网站,然后让客户端使用直接URL 下载它们。这允许IIS管理流式传输,为您的网站保存请求并节省大量内存。

或(不太可能)

看到您使用的是Visual Studio 2013,这听起来非常像Page Inspector问题。

当您使用IIS ExpressVisual Studio Page Inspector缓存所有的响应数据(包括文件的)运行您的网站时会发生什么情况要使用大量内存。尝试添加:

<appSettings>
    <add key="PageInspector:ServerCodeMappingSupport" value="Disabled" />
</appSettings>

web.config停用Page Inspector以查看是否有帮助。

TL; DR

在本地缓存大文件,让客户端直接下载文件。让IIS为您处理艰苦的工作。

答案 5 :(得分:0)

我建议尝试Ionic.Zip库。我在我们的一个站点中使用它,需要将多个文件下载到一个单元中。

我最近用一组文件测试了它,其中一个文件大到600MB:

  • 压缩/压缩文件夹的总大小:260MB
  • 解压缩文件夹的总大小:630MB
  • 下载期间内存使用量从350MB增加到650MB
  • 总时间:1m 10s下载,无VPN