使用DirectorySearcher.FindAll()时内存泄漏

我有一个漫长的运行过程,需要经常在Active Directory上进行大量查询。 为此,我一直使用System.DirectoryServices命名空间,使用DirectorySearcher和DirectoryEntry类。 我注意到应用程序中存在内存泄漏。

它可以使用以下代码重现:

while (true) { using (var de = new DirectoryEntry("LDAP://hostname", "user", "pass")) { using (var mySearcher = new DirectorySearcher(de)) { mySearcher.Filter = "(objectClass=domain)"; using (SearchResultCollection src = mySearcher.FindAll()) { } } } } 

这些类的文档说如果没有调用Dispose(),它们将泄漏内存。 我试过没有处理过,它只是泄漏了更多的内存。 我已经使用框架版本2.0和4.0对此进行了测试。之前是否有人遇到此问题? 有没有解决方法?

更新:我尝试在另一个AppDomain中运行代码,它似乎也没有帮助。

虽然它可能很奇怪,但只有在您对搜索结果不做任何操作时才会发生内存泄漏。 如下修改问题中的代码不会泄漏任何内存:

 using (var src = mySearcher.FindAll()) { var enumerator = src.GetEnumerator(); enumerator.MoveNext(); } 

这似乎是由具有延迟初始化的内部searchObject字段引起的,使用Reflector查看SearchResultCollection:

 internal UnsafeNativeMethods.IDirectorySearch SearchObject { get { if (this.searchObject == null) { this.searchObject = (UnsafeNativeMethods.IDirectorySearch) this.rootEntry.AdsObject; } return this.searchObject; } } 

除非初始化searchObject,否则dispose不会关闭非托管句柄。

 protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (((this.handle != IntPtr.Zero) && (this.searchObject != null)) && disposing) { this.searchObject.CloseSearchHandle(this.handle); this.handle = IntPtr.Zero; } .. } } 

在ResultsEnumerator上调用MoveNext会调用集合上的SearchObject,从而确保它也正确处理。

 public bool MoveNext() { .. int firstRow = this.results.SearchObject.GetFirstRow(this.results.Handle); .. } 

我的应用程序中的泄漏是由于某些其他非托管缓冲区未正确释放而我所做的测试具有误导性。 问题现在解决了。

托管包装器并没有泄漏任何东西。 如果您不调用Dispose则在垃圾回收期间仍将回收未使用的资源。

但是,托管代码是基于COM的ADSI API之上的包装器,当您创建DirectoryEntry ,底层代码将调用ADsOpenObject函数 。 处理DirectoryEntry或在完成期间释放返回的COM对象。

将ADsOpenObject API与一组凭据和WinNT提供程序一起使用时,会出现文档内存泄漏 :

  • 所有版本的Windows XP,Windows Server 2003,Windows Vista,Windows Server 2008,Windows 7和Windows Server 2008 R2都会发生此内存泄漏。
  • 仅当您将WinNT提供程序与凭据一起使用时,才会发生此内存泄漏。 LDAP提供程序不会以这种方式泄漏内存。

但是,泄漏只有8个字节,据我所知,您使用的是LDAP提供程序,而不是WinNT提供程序。

调用DirectorySearcher.FindAll将执行需要大量清理的搜索。 此清理在DirectorySearcher.Dispose完成。 在您的代码中,此清理在循环的每次迭代中执行,而不是在垃圾回收期间执行。

除非LDAP ADSI API中确实存在未记录的内存泄漏,否则我可以提出的唯一解释是非托管堆的碎片。 ADSI API由进程内COM服务器实现,每次搜索都可能在进程的非托管堆上分配一些内存。 如果此内存碎片化,则在为新搜索分配空间时,堆可能必须增长。

如果我的假设是正确的,一个选项是在单独的AppDomain中运行搜索,然后可以回收以卸载ADSI并回收内存。 但是,即使内存碎片可能会增加对非托管内存的需求,我也预计会有一个上限,需要多少非托管内存。 除非你有泄漏。

此外,您可以尝试使用DirectorySearcher.CacheResults属性 。 将其设置为false删除泄漏吗?

由于实现限制,SearchResultCollection类在垃圾回收时无法释放所有非托管资源。 要防止内存泄漏,必须在不再需要SearchResultCollection对象时调用Dispose方法。

http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx

编辑:

我已经能够使用perfmon重新显示明显的泄漏,并在测试应用程序的进程名称上添加Private Bytes的计数器(Experiments.vshost for me)

当应用程序循环时,Private Bytes计数器将稳步增长,它开始大约40,000,000,然后每几秒增长大约一百万字节。 好消息是,当您终止应用程序时,计数器会恢复正常(35,237,888),因此最终会进行某种清理。

我附上了一个屏幕截图,显示了当它漏水时perfmon的样子 perfmon内存泄漏的截图

更新:

我尝试了一些解决方法,比如禁用DirectoryServer对象上的缓存,但它没有帮助。

FindOne()命令不会泄漏内存,但我不确定你要做什么才能使该选项适合你,可能不断编辑filter,在我的AD控制器上,只有一个域,所以findall&findone给出相同的结果。

我还尝试排队10,000个线程池工作者来制作相同的DirectorySearcher.FindAll()。 它完成得更快,但它仍然泄漏内存,实际上私有字节上升到大约80MB,而不是仅仅48MB的“正常”泄漏。

因此,对于此问题,如果您可以让FindOne()为您工作,您有一个解决方法。 祝好运!

你尝试过usingDispose()吗? 来自这里的信息

更新

尝试调用de.Close(); 在使用结束之前。

我实际上没有一个Active Domain服务来测试这个,抱歉。

找到了一个快速而肮脏的方法。

我的程序中存在类似的问题,内存增长但是通过更改.GetDirectoryEntry()。属性(“cn”)。值为

.GetDirectoryEntry()。属性(“cn”)。Value.ToString带有if before hand以确保.value不为null

我能够告诉GC.Collect摆脱我的foreach中的临时值。 看起来.value实际上是保持对象存活而不是允许它被收集。