追踪 .NET Windows 服务的内存泄漏问题

16

在将我的Windows服务安装到生产环境之前,我正在寻找可靠的测试,以确保我的代码不包含内存泄漏。 但是,我在网络上能够找到的只有使用任务管理器查看已使用内存或使用一些付费的内存分析工具。

据我了解,查看任务管理器并不能真正帮助确认内存泄漏(如果有的话)。

  1. 如何确认是否存在内存泄漏?

  2. 是否有免费的工具可以找到内存泄漏的源头?

注意:我正在使用.Net Framework 4.6和Visual Studio 2015 Community。


这完全取决于你的服务具体是做什么的。 - Evk
@Evk 不管服务做什么,我该如何确保服务中没有内存泄漏? - Mhd
我的意思是,真正测试内存泄漏的程序很难,特别是Windows服务。你必须在一段时间内广泛地使用它。增长的内存本身并不表示有泄漏,因为如果没有压力(没有内存压力),GC可能决定不收集任何东西。所以你能做的最好的事情就是编写没有泄漏的良好代码,并监视应用程序的内存使用情况,如果内存达到某个阈值,则收集内存转储并使用分析器进行分析。 - Evk
7个回答

11

你可以使用任务管理器。

但是...

免费工具 - ".Net CLR Profiler"

这个免费工具来自微软,非常棒。对于所有存在引用泄漏的程序,这都是必须使用的工具。可以在微软的网站上搜索到。

引用泄漏是指你忘记将对象引用设置为null或它们永远不会离开作用域,并且这几乎和垃圾回收语言一样容易发生,如列表进行累加而不清除、事件处理程序指向委托等。

这是与内存泄漏相当的垃圾回收问题,具有相同的结果。此程序告诉您哪些引用占用了大量内存,您可以知道这是否应该这样,如果不是,则可以找到并解决问题!

它甚至具有一个很酷的可视化界面,显示对象分配了哪些内存(因此您可以追踪错误)。如果需要解释,我相信有youtube视频提供介绍。

Memory Usage Visualization

维基百科页面,包含下载链接...

注意:您可能需要将应用程序运行为非服务模式才能使用此工具。它会首先启动并运行您的应用程序。您可以使用TopShelf或仅将实现服务集成的guts放在一个dll中,该dll从实现服务集成(服务主机模式)的EXE运行。


3

虽然托管代码不需要直接管理内存,但您仍然需要管理实例。这些实例“占用”内存。这完全取决于这些实例的使用方式,保持它们在您不希望它们存在时保持活动状态。

只是众多例子之一:错误使用可释放类可能导致大量实例占用内存。对于Windows服务,实例的缓慢但稳定增加最终可能导致过多的内存使用。

是的,有一个分析内存泄漏的工具。 它只是不免费。但是,您可以在7天试用期内识别出问题。

我建议看一下 .NET Memory Profiler

它非常适合开发期间分析内存泄漏。它使用快照的概念来比较新实例、已释放实例等。这非常有助于了解您的服务如何使用其内存。然后,您可以深入了解为什么会创建新实例或保持活动状态。

是的,您可以测试以确认是否引入内存泄漏。 但是,仅仅拿来就用通常并没有太大的用处。这是因为没有人能预料到运行时会发生什么。该工具可以分析您的应用程序是否存在常见问题,但这并不保证。

然而,您可以使用此工具将内存消耗集成到单元测试框架中,如NUnitMSTest


2
当然,内存分析工具是首选的工具之一,但它只能告诉您实例是否不断增加。您仍然想知道它们不断增加是否正常。此外,一旦您确定某些实例无故增加(也就是说,您有一个泄漏),您将想要确切地知道哪些调用树导致了它们的分配,以便您可以排除分配它们的代码并修复它们,以便最终释放它们。
以下是多年来我处理此类问题时收集的一些知识:
1.尽可能将服务作为常规可执行文件进行测试。尝试将服务作为实际服务进行测试只会使事情变得过于复杂。
2.养成在做某件事情的范围结束时明确撤消所有操作的习惯。例如,如果您向某个观察者的事件注册了一个观察者,则始终应该在某个时间点(观察者或被观察者的处置?)注销它。理论上,垃圾回收应通过收集相互连接的观察者和被观察者的整个图形来处理它,但实际上,如果您不改掉忘记撤消您所做的事情的习惯,那么您将会出现内存泄漏。
3.尽可能使用IDisposable,并使您的析构函数报告某个人是否忘记调用Dispose()。有关此方法的更多信息,请参见:强制释放与“Dispose-disposing”丑闻。披露:我是该文章的作者。
4.在程序中有定期检查点,您可以在其中释放应该释放的所有内容(就像程序正在执行有序关闭以终止一样),然后强制进行垃圾回收以查看您是否有任何泄漏。
5.如果某个类的实例似乎在泄漏,则使用以下技巧发现导致其分配的精确调用树:在该类的构造函数中,分配一个异常对象而不抛出它,获取异常的堆栈跟踪,并将其存储。如果您后来发现此对象已泄漏,则具有必要的堆栈跟踪。只是不要对太多对象这样做,因为分配异常并从中获取堆栈跟踪非常缓慢,只有微软知道原因。

2
你可以尝试使用免费的Memoscope内存分析器。

https://github.com/fremag/MemoScope.Net

我不认同使用任务管理器来检查是否存在内存泄漏的说法。垃圾收集器的问题在于,它可以根据启发式原则决定在内存暴增后保留内存而不将其返回给操作系统。您可能有2GB的提交大小,但其中90%可以空闲。
您应该使用VMMAP 来检查测试期间进程包含哪种类型的内存。您不仅拥有托管堆,还有未经处理的堆、私有字节、堆栈(线程泄漏)、共享文件等需要跟踪的内容。
VMMap还具有命令行界面,这使得在规律间隔内创建快照并稍后检查成为可能。如果您有内存增长,您可以找出泄漏的内存类型,并根据泄漏类型选择不同的调试工具方法。

0

我不会说垃圾回收器是绝对可靠的。有时候它会在不知不觉中失效,而且问题并不总是那么直接明显。内存流是内存泄漏的常见原因之一。你可以在一个上下文中打开它们,但它们可能永远不会被关闭,即使使用已经包含在using语句中(这是一种应该在使用范围结束后立即清理的可处理对象的定义)。如果你遇到由于内存耗尽而导致的崩溃,Windows会创建转储文件供你查看。

在此输入链接描述

这绝不是一件有趣或容易的事情,而且相当繁琐,但这往往是你最好的选择。

容易造成内存泄漏的常见领域包括使用System.Drawing dll的任何内容、内存流以及如果你正在进行一些严重的多线程操作。


0

如果您使用Entity Framework和DI模式,可能使用Castle Windsor,那么您很容易出现内存泄漏。

主要的做法是在任何地方都使用using(){}语句,以自动标记对象为已处理。

此外,您还需要关闭Entity Framework上的自动跟踪,仅在读取而不是写入时使用。最好隔离您的写入,在这一点上使用using(){},获取带有跟踪的dbContext,写入您的数据。

如果您想调查堆上的内容。我使用过的最好的工具是RedGate ANTS http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/solving-memory-problems/getting-started,虽然不便宜但它确实有效。

然而,通过在任何地方都使用using(){}模式(不要创建静态或单例DbContext,并且永远不要在大量更新的循环中使用一个上下文,尽可能经常处理它们!),那么您会发现内存通常不是问题。

希望这可以帮助到您。


-2

除非你在处理非托管代码,否则我敢说你不必担心内存泄漏。托管代码中的任何未引用对象都将被垃圾回收器移除,而在.NET框架中找到内存泄漏的可能性,我会说你应该算是非常幸运(或者不幸)。你不必担心内存泄漏。

然而,如果对象的引用从未释放,您仍然可能遇到不断增长的内存使用情况。例如,假设您保留了一个内部日志结构,并且您只是不断向日志列表添加条目。那么每个条目仍然从日志列表中引用,因此永远不会被收集。

根据我的经验,您绝对可以使用任务管理器作为指示器,以确定系统是否存在增长问题;如果内存使用量稳步上升,则知道您有问题。如果它增长到一定程度,但最终收敛到一定大小,则表示它已达到其操作阈值。

如果您想更详细地查看托管内存使用情况,可以下载由Microsoft开发的进程资源管理器。它仍然相当粗略,但比任务管理器提供了更好的统计视图。


这是一个好答案。它回答了你的第一个问题:你可以使用任务管理器观察内存使用行为。如果内存使用量从未下降,这表明你有问题。根据我的经验,这个方法很有效,不需要其他工具。关键是垃圾收集器只会删除未引用的对象,因此请确保在完成后销毁所有对象的引用。在整个服务中实现Dispose模式。每当可用时调用Dispose并将大型对象设置为null。这是防止内存问题的最佳方法。 - user4864425
@Micael 这不是真的。内存泄漏也可能由托管代码引起。想想未处理的对象、委托订阅、弱引用... - Mhd
未处理的对象最终将被垃圾回收器处理。我想这归结于定义。内存泄漏通常指未被释放的内存块,但没有任何东西与之相关。链接每个块引用另一个块的内存块不被视为内存泄漏,而是一种低效的结构;您不希望垃圾回收器释放内存,因为您可能有理由保留它。它会占用内存,但不会造成内存泄漏。 - Micael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接