【问题标题】:Relation Between iis worker process memory usage and disposing objects?iis工作进程内存使用和处理对象之间的关系?
【发布时间】:2019-09-15 19:38:22
【问题描述】:

我们正在通过以下代码通过 Entity Framework 从 Sql Server 获取大量数据

    using (var db=new Entities())
    {
        var list = db.spGetRecs().ToList();

       ///rest of the codes
    }

记录数大约是 400 万,之后 iis 变得如此繁重,它的内存使用量达到了 690 mb 左右,并且工作进程根本不释放内存。 您可以简单地想到多个用户使用这么多内存的情况,然后当然会发生out of memory exception

我要在这里提出三个问题:

1.首先,为什么iis工作进程不释放内存?

2.其次,我们如何强制iis工作进程在处理完数据后释放内存?

3.第三,为什么尽管我处理了所有与这个庞大数据相关的对象,但它对iis的内存使用没有任何影响?!那么在与windows进程无关的情况下处理对象有什么意义呢?!

我没有编写整行代码,因为我不想使问题复杂化,也不想分散您对这个问题背后可能具有挑战性的概念的注意力。

顺便说一句,在我调用垃圾收集器 GC.Collect() 后,它从 iis 工作进程中释放了大约 20Mb。

【问题讨论】:

  • 对你自己的问题是你是如何处理所有对象的,因为看起来你做得不对。有大量关于如何分析内存不足异常的好工具(内存分析器)和文章,但我认为您并没有真正利用它们。
  • 您是否需要同时在内存中记录数百万条记录,您可以应用任何.Where(..) 调用吗?您还可以在没有ToList() 的情况下完成您尝试做的工作,而是在 foreach 中完成您的工作,这样您就不需要同时在内存中存储整个列表?此外,您是否正在编辑这些对象并将它们保存回数据库,如果您正在执行只读操作,您可以对 EntityFramework 调用进行调整以使其使用更少的内存。
  • 是的,我需要所有这些……过滤也无关紧要……我的问题有点概念性。

标签: c# asp.net entity-framework iis


【解决方案1】:

即使导致内存分配的对象已被释放,进程仍然可以报告内存分配过多。内存可能仍被视为保留但未提交,但老实说,通过释放死对象深入了解垃圾收集的工作原理并不是我真正需要担心的事情,除非在追踪无法解释的不断增长的内存使用时。

第一步应该始终最小化内存占用以适应并发请求。对于大型请求,您应该考虑实现一个带有后台进程的请求队列,以确保以在任何给定时间处理有限数量的并发请求的方式处理这些请求,或者使用不同服务器上的资源,以免影响网络服务器的响应能力。

最小化内存占用大小提示:

  1. 使用包含轻量级实体定义的有界上下文来处理流程所需的最少字段。例如,如果一个实体通常包含 50 多个列,其中一些字段是您不需要的大字符串、二进制数据等,则具有一个实体定义的有界上下文仅引用您需要的 10 个列将节省记忆。
  2. 合理地提取数据。利用分页在任何时候只检索可管理的数据子集。 IE。一次 1000 条记录。利用SkipTake 以及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 实例),但仍然没有出现内存不足的异常。

【讨论】:

  • 谢谢@Steve,我感谢您在这个主题上分配的时间,但目前还不清楚并且有点模糊,一个进程如何与它的对象相关,以及为什么进程没有提交分配对象死亡时的内存块。
  • 我在上面添加了一个编辑,其中包含关于从 DbContext 加载大量数据以及垃圾收集/处理行为的测试观察。进程的可见已提交 RAM 使用量不会立即下降,即使在使用故意收集时也是如此,但是 RAM 可供进程使用,无论它随着时间的推移加载了多少次迭代/块。
【解决方案2】:

我认为关于dispose object和release memory存在很大的误解。

说到iis工作进程的内存管理理论,我们可以把它想象成下面的场景,当一个请求发给iis时,它需要一块内存来存储我们的请求(线程)对象,Os分配这块内存iis工作进程的块,当我们创建和填充对象时,实际上我们正在填充这些内存块,当我们处理我们的对象时,实际上我们正在从我们的对象中清除这些块,我们负责我们放入这些块中的数据,而不是内存块本身。

管理它是操作系统的责任,当我们处理对象时,这个块是空的,但是Os不会从工作进程中重新获取它,因为它将被另一个请求(线程)填充,除非有shortage of RAM,在这个情况Os重新获取未被任何活动对象占用的块数量,例如当我们从sql执行繁重的查询时,它需要内存来执行,那么将从iis工作进程未占用的块中提供ram短缺。

当然你上面红色的场景只是我的理解,但我很乐意阅读你的观点。

【讨论】:

    【解决方案3】:

    当一个应用程序启动并分配内存时,通常操作系统系统会管理内存,如果另一个应用程序需要额外的内存,它会释放内存。真实的例子就像一片广阔的土地,每个应用程序在特定的空间中建立一个家,当它被摧毁时,土地仍然存在并且不会被摧毁。因此,如果新应用程序需要额外的内存,操作系统会为它提供空闲应用程序的内存处置。

    【讨论】:

      猜你喜欢
      • 2015-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多