即使导致内存分配的对象已被释放,进程仍然可以报告内存分配过多。内存可能仍被视为保留但未提交,但老实说,通过释放死对象深入了解垃圾收集的工作原理并不是我真正需要担心的事情,除非在追踪无法解释的不断增长的内存使用时。
第一步应该始终最小化内存占用以适应并发请求。对于大型请求,您应该考虑实现一个带有后台进程的请求队列,以确保以在任何给定时间处理有限数量的并发请求的方式处理这些请求,或者使用不同服务器上的资源,以免影响网络服务器的响应能力。
最小化内存占用大小提示:
- 使用包含轻量级实体定义的有界上下文来处理流程所需的最少字段。例如,如果一个实体通常包含 50 多个列,其中一些字段是您不需要的大字符串、二进制数据等,则具有一个实体定义的有界上下文仅引用您需要的 10 个列将节省记忆。
- 合理地提取数据。利用分页在任何时候只检索可管理的数据子集。 IE。一次 1000 条记录。利用
Skip 和Take 以及OrderBy 子句,然后查看返回的记录数以评估是否还有更多页面要检索。 (而不是依赖可能昂贵的 Count 查询。)
后台进程是处理繁重查询/处理的更好解决方案。如果它们可以针对只读副本而不是主数据库运行,那就更好了。您的 Web 服务器可以接收具有给定参数集的请求,而不是启动会使服务器饿死的昂贵查询,它们可以简单地在处理 Queue 表中创建一条记录,以向后台进程发出信号以获取该记录并处理它。该后台进程可以在完全不同的服务器或服务器场上运行。如果用户正在等待结果,Web 服务器可以定期轮询后台工作人员的状态更新,并在处理完成后显示处理 Queue 表或相关结果表中的结果。队列可以由多个工作人员提供服务,前提是您有一个同步上下文来协调他们并分配工作以避免多个工作人员承担同一个工作。
编辑:
当涉及到垃圾收集时,您会担心,因为进程(垃圾收集器)正在保持内存提交。您期望如果有问题的代码需要分配 1GB 的内存,那么在 dispose 甚至 GC.Collect() 之后,内存使用量将下降 1GB。它没有。内存仍将显示为已提交给进程,但仍可用于该进程的代码。您可以通过运行一个巨大的查询并让生成的实体/数据过期来测试这一点。例如,我有一个来自另一个 SO 问题的最新测试数据集,我在其中填充了 3M 行数据。由于我的系统处于相当大的负载下,我尝试使用以下方法将所有 3M 行读入内存:
using (var context = new TestDbContext())
{
var test = context.Messages.ToList();
Assert.IsTrue(true);
}
这持续了一段时间,然后根据进程监视器显示大约 1.7GB 的内存不足异常。所以我把它改成了:
using (var context = new TestDbContext())
{
var test = context.Messages.Take(1000000).ToList();
Assert.IsTrue(true);
}
这使用 ~500MB 的 RAM 完成。所以我让它连续执行 4 次:
using (var context = new TestDbContext())
{
var test = context.Messages.Take(1000000).ToList();
Assert.IsTrue(true);
}
using (var context = new TestDbContext())
{
var test = context.Messages.Take(1000000).ToList();
Assert.IsTrue(true);
}
using (var context = new TestDbContext())
{
var test = context.Messages.Take(1000000).ToList();
Assert.IsTrue(true);
}
using (var context = new TestDbContext())
{
var test = context.Messages.Take(1000000).ToList();
Assert.IsTrue(true);
}
现在,如果最初的 1M “泄漏”,那么我的应用程序进程将耗尽内存。除非它没有。第一个攀升至~500MB,第二个从那里攀升至~1.1GB 第三个达到1.6GB。下一个开始爬升,然后锯齿状下降到 ~1GB 并继续爬回 ~1.5GB。随后的每次读取都被锯齿化,以保持在 1.4 到 1.5GB 之间。我重复了 6、10 次阅读。有时它锯齿状地低至 200MB,有时几乎没有。没有内存不足异常或交换到磁盘的性能下降。调用 GC.Collect() 不会明显释放内存,即使重复调用或在每个 using 块之后调用也是如此。内存被“释放”并在每个后续调用中重复使用。每次运行或额外读取块的性能是一致的。
并行运行多个测试实例时,您可能会开始看到性能下降,因为可以看到每次运行都分配了尽可能多的内存,达到了限制。所需的组合 RAM 超过了系统上的可用内存,因此每个 RAM 轮流将其内存块缓存到磁盘。结果是性能显着下降,只有 2 或 3 个这些测试并行运行(不同的 VS 实例),但仍然没有出现内存不足的异常。