C#.NET内存泄漏:GC阶段#1和阶段#2运行时的锯齿内存使用情况

时间:2013-03-13 17:58:24

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

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

此应用似乎有内存泄漏

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

    特点: 启动时,程序使用~30MB。随着时间的推移,任务管理器中的内存使用将开始进行,首先只是略微,在50到150MB之间,并最终变得更糟,在200MB和1GB +之间振荡。当发生这种情况时,它会在一两秒钟内发生几次,然后在接下来的10-20秒左右稳定在150MB左右。

    我一直在尝试使用内存分析来捕捉这种行为。到目前为止,我一直没有成功,我无法将应用程序带到pogo或在内存使用情况下振荡,就像探测器不在观看时一样。 然而,我一直注意到内存使用上的方波模式,因为垃圾收集器阶段1和2运行看起来非常类似于我在任务管理器中看到的,除了方波中的内存使用振荡是宽10MB,而不是800MB +(200MB到1GB +)。现在,根据谷歌图片,正常运行的应用程序中的垃圾收集看起来更像是锯齿波,而不是方形。

    我坦率地看不出我的应用程序在一秒钟之内可以在200MB到1GB +内存使用量之间进行任何操作,而不会将CPU加速到100%。

    我已经阅读了垃圾收集+事件处理之间可能出现的一些问题,但我有几条路可以调查,并且正在努力缩小哪一个花时间。我在.NET上的速度仍然很慢,并没有为运行C的嵌入式设备开发出“直觉”,这通常可以帮助我过滤我应该先调查的内容。 如果FEELS喜欢的话可能是某些事件处理程序正在丢失并重新获得[大量数据]的引用(我不知道这甚至会发生什么?)看到内存使用情况似乎很快就会回升到1GB垃圾收集器运行并将内存使用量减少到200MB。

    此应用的早期版本没有这些问题。自那时以来我做出的两项改变包括

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

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

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

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

1 个答案:

答案 0 :(得分:4)

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

我认为您可以尝试以下内容:

  1. 运行该应用程序并重现该问题。当私有工作内存集达到非常高的值时,右键单击任务管理器上的进程并选择“创建转储文件”。与在线分析应用程序相比,这将更具侵入性。
  2. 下载WinDBG并运行它。
  3. 打开内存转储,转到“文件”菜单并选择“打开转储文件”(我不记得菜单选项的名称到底是什么......应该很容易发现)。
  4. 运行以下命令:

    .symfix

    .loadby sos clr

    !dumpheap -type [YourAssemblyNameSpacePrefix] -stat

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

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

    !dumheap -type [TheFullObjectTypeName]

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

    !gcroot [InstanceAddress]

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

    如果您没有看到任何与您自己的类型有关的怪异,请将步骤4中的!dumpheap命令更改为:!dumpheap -stat。这样您就不会按类型过滤,也会看到CLR类型和第三方库类型。

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