【发布时间】:2014-11-17 13:16:27
【问题描述】:
如果我只是浏览应用程序上的一些页面,它大约有 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 文件。此外,很多时候,即使页面立即加载,内存使用量也会持续增加几秒钟。
【问题讨论】:
-
试用 Jetbrains dotMemory 4.1 beta www.jetbrains.com/dotmemory/download
-
您能发布您的整个存储库吗? (有些人认为整个 Repository 模式是一种反模式,我就是其中之一,但这不是重点)。我偷偷怀疑
_unitOfWork.Repository<App>().Update(app);可能会导致整个 DbSet 被加载,但我无法从您发布的代码中看到它。 -
如果将文件大小减半,内存使用峰值会下降吗?这将帮助您确定是文件本身还是调用文件检索的代码导致了问题。
-
@krisdyson 是的,内存使用峰值确实下降了。它不会破坏 1GB。
-
将 GC.Collect() 添加到 Dispose 方法以进行测试。如果泄漏仍然存在,那就是真正的泄漏。如果它消失了,它只是延迟了 GC。
标签: c# asp.net asp.net-mvc entity-framework memory