【发布时间】:2013-03-01 20:06:52
【问题描述】:
我有一个事件驱动的应用程序,我的任务是维护。 每 30 秒大约有 100 个事件在不同的计时器上运行。随着时间的推移,这些事件会变成每秒大约 1-3 个事件的恒定流。 内存使用似乎不依赖于任何给定秒内触发的事件数。 每个事件都从 Web 服务轮询数据,使用 LINQ2SQL DataContext 对照先前轮询的数据检查数据(完成后我不会处理或清空 DataContext),如果数据不同,则更新数据库并将新数据推送为通过 TCP 向接收者服务发送 XML 消息。
此应用似乎存在内存泄漏
- 仅在运行 30m+ 后才出现(调试或发布)
- 分析时不会显示 [我正在使用 .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。
此应用的早期版本没有这些问题。从那以后我做了两个改变,包括
- 使用 LINQ2SQL 而不是我们自己的数据管理器(它有一个我们用来执行硬编码 SQL 语句的 ADORecordSetHelper 对象)
- 更改我们用来将 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