【问题标题】:C# .NET memory leak: sawtooth memory usage when GC stage#1 and stage#2 runC# .NET 内存泄漏:GC 阶段#1 和阶段#2 运行时的锯齿状内存使用情况
【发布时间】:2013-03-01 20:06:52
【问题描述】:

我有一个事件驱动的应用程序,我的任务是维护。 每 30 秒大约有 100 个事件在不同的计时器上运行。随着时间的推移,这些事件会变成每秒大约 1-3 个事件的恒定流。 内存使用似乎不依赖于任何给定秒内触发的事件数。 每个事件都从 Web 服务轮询数据,使用 LINQ2SQL DataContext 对照先前轮询的数据检查数据(完成后我不会处理或清空 DataContext),如果数据不同,则更新数据库并将新数据推送为通过 TCP 向接收者服务发送 XML 消息。

此应用似乎存在内存泄漏

  1. 仅在运行 30m+ 后才出现(调试或发布)
  2. 分析时不会显示 [我正在使用 .NET Memory Profiler 4.5]

特点: 启动时,程序使用 ~30MB。随着时间的推移,任务管理器中的内存使用量将开始急剧上升,起初只是轻微的,介于 50 到 150MB 之间,最终会变得更糟,在 200MB 和 1GB+ 之间波动。发生这种情况时,它会在一两秒内发生几次,然后在接下来的 10-20 秒左右稳定在 ~150MB。

我一直在尝试使用内存分析来捕捉这种行为。到目前为止,我一直没有成功,当分析器不观看时,我无法让应用程序在内存使用量附近的任何地方进行 pogo 或振荡。 但是,当垃圾收集器阶段 1 和 2 运行时,我注意到内存使用的方波模式看起来与我在任务管理器中看到的非常相似,除了方波中的内存使用振荡是10MB 宽,而不是 800MB+(200MB 到 1GB+)。现在,根据 Google 图片,正常运行的应用程序中的垃圾收集看起来更像是锯齿波而不是方形。

坦率地说,我看不出我的应用会在一秒钟内占用 200MB 到 1GB+ 的内存,并且不会使 CPU 达到 100%。

我已经阅读了垃圾收集和事件处理之间可能出现的一些问题,但我有几条路径可以调查,并试图缩小要花时间在哪一条上的范围。我在 .NET 上的速度仍然很慢,并且还没有开发出我对运行 C 的嵌入式设备的“直觉”,这通常可以帮助我过滤我应该首先调查的内容。 如果感觉可能是某些事件处理程序正在丢失并重新获得对 [大量数据] 的引用(我不知道这怎么可能发生?),因为内存使用量似乎在垃圾收集器运行并将内存使用量降至 200MB。

此应用的早期版本没有这些问题。从那以后我做了两个改变,包括

  1. 使用 LINQ2SQL 而不是我们自己的数据管理器(它有一个我们用来执行硬编码 SQL 语句的 ADORecordSetHelper 对象)
  2. 更改我们用来将 TCP XML 消息发送到接收器的软件。 由于我们在 #2 中所做的事情很简单,它可能是问题的根源,但这种内存使用行为让我不这么认为。

我想我现在的主要问题是

  • 在从创建它们的方法返回之前,是否应该在我的 LINQ2SQL DataContexts 上调用 dispose?
  • 我应该将它们清空吗?
  • 如果在创建 DataContext 后方法中某处发生异常,是否会导致 DataContext 无限期地保留在内存中?
  • 如果我将 LINQ 查询的结果存储到值类型(即 int 而不是 var),那么它是延迟加载的,还是在使用变量时延迟加载的?
  • 事件驱动的框架有多大可能假设性地丢失和重新获得引用?

编辑:事件具有基于实例的订阅,如讨论的here,并且在应用程序的生命周期内永远不会取消订阅。

edit2:终于设法在分析器中捕捉到它,似乎是一个 200MB 的 system.string 以某种方式创建。感谢大家排除 GC 行为。

【问题讨论】:

  • DataContext 是 IDisposable。使用完毕后正确处理(不一定是创建它们的方法,请参阅using)。没有例外!我不敢说这是问题所在,但请确保正确访问/处置 DC - 对于 Web 服务或 ASP.NET 页面,创建 one DC 以用于请求并在请求完成时处理它。
  • 是什么让你称之为内存泄漏?应用程序最终会耗尽内存吗?
  • @JohnSaunders 接收器服务在同一台机器上运行,我相信它的一些崩溃是由于 OOM 异常,但没有日志来证明这一点。
  • 那么你很可能试图解决错误的问题。
  • 可能有很多错误...但是:创建我称之为“僵尸对象”的唯一方法是在对象已被声明为垃圾的析构函数代码中做一些奇怪的事情但可能会再次成为非垃圾。编辑:此外,缺少 IDisposable.Dispose 可能会由于析构函数而延迟垃圾收集。请参阅“GC.SuppressFinalize”。

标签: c# .net linq-to-sql memory-leaks


【解决方案1】:

大多数时候,内存泄漏是由对象之间的奇怪引用引起的(这里还包括事件和委托)。

我认为您可以尝试以下方法:

  1. 运行应用程序并重现问题。当内存的私有工作集达到非常高的值时,右键单击任务管理器上的进程并选择“创建转储文件”。这比实时分析应用程序的干扰要小得多。
  2. 下载 WinDBG 并运行它。
  3. 通过转到“文件”菜单并选择“打开转储文件”来打开内存转储(我不记得确切的菜单选项名称是什么......不过应该很容易发现)。
  4. 运行以下命令:

    .symfix

    .loadby sos clr

    !dumpheap -type [YourAssemblyNameSpacePrefix] -stat

  5. 最后一个命令将为您提供内存中不是 CLR 类型的所有实例,仅提供您的类型。查看具有大量实例的类型并尝试查看是否有任何不正确的地方。

  6. 如果您看到大量相同类型的对象,请运行以下命令,该命令将显示所有实例的地址:

    !dumheap -type [TheFullObjectTypeName]

    您需要选择一个实例地址。现在运行以下命令来查看对该实例的引用:

    !gcroot [实例地址]

对不同的实例重复步骤 6 几次,以便您可以确认泄漏来自同一个地方或帮助您确定导致这些实例未被收集的原因(仍然被其他对象引用)。​​

如果您没有发现自己的类型有任何奇怪之处,请将步骤 4 中的 !dumpheap 命令更改为:!dumpheap -stat。这样您就不会按类型进行过滤,您还会看到 CLR 类型和第三方库类型。

这有点复杂,但希望我能给你一个方法来帮助你弄清楚如何找到内存泄漏。

【讨论】:

    猜你喜欢
    • 2014-08-14
    • 2015-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-06
    • 2018-12-27
    • 2014-07-03
    相关资源
    最近更新 更多