如果我只浏览应用程序上的某些页面,它大约为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的一些屏幕截图:
我相信这可能是问题,或者我走错了路。为什么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,这就是我得到的。
红色圆圈表示有时在一次访问时会发生多次上升和下降。蓝色圆圈表示下载40MB文件。绿色圆圈表示下载140MB文件。此外,在很多时候,即使页面立即加载,内存使用量也会持续增加几秒钟。
答案 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
中。
此问题的解决方案是不使用EF
和ASP .NET
直接从数据库中流式传输大型文件。更好的解决方案是使用后台进程将大型文件本地缓存到网站,然后让客户端使用直接URL 下载它们。这允许IIS
管理流式传输,为您的网站保存请求并节省大量内存。
或(不太可能)
看到您使用的是Visual Studio 2013
,这听起来非常像Page Inspector
问题。
当您使用IIS Express
从Visual Studio
Page Inspector
缓存所有的响应数据(包括文件的)运行您的网站时会发生什么情况要使用大量内存。尝试添加:
<appSettings>
<add key="PageInspector:ServerCodeMappingSupport" value="Disabled" />
</appSettings>
到web.config
停用Page Inspector
以查看是否有帮助。
TL; DR
在本地缓存大文件,让客户端直接下载文件。让IIS为您处理艰苦的工作。
答案 5 :(得分:0)
我建议尝试Ionic.Zip库。我在我们的一个站点中使用它,需要将多个文件下载到一个单元中。
我最近用一组文件测试了它,其中一个文件大到600MB: