1

我有一个事件驱动的应用程序,我的任务是维护。每 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 而不是我们自己的数据管理器(它有一个 ADORecordSetHelper 对象,我们用来执行硬编码的 SQL 语句)
  2. 更改我们用来将 TCP XML 消息发送到接收器的软件。由于我们在 #2 中所做的事情很简单,它可能是问题的根源,但这种内存使用行为让我不这么认为。

我想我此时的主要问题是

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

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

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

4

1 回答 1

4

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

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

  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 类型和第三方库类型。

这有点复杂,但希望我能够为您提供一种方法来帮助您弄清楚如何查找内存泄漏。

于 2013-03-14T11:08:16.177 回答