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

C# 使用DirectorySearcher.FindAll()时内存泄漏,c#,.net,memory-leaks,directoryservices,adsi,C#,.net,Memory Leaks,Directoryservices,Adsi,我有一个长时间运行的过程,需要经常在Active Directory上执行大量查询。为此,我一直在使用System.DirectoryServices命名空间,使用DirectorySearcher和DirectoryEntry类。我注意到应用程序中存在内存泄漏 可使用以下代码复制: while (true) { using (var de = new DirectoryEntry("LDAP://hostname", "user", "pass")) { usi

我有一个长时间运行的过程,需要经常在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(),它们将泄漏内存。我也尝试过不使用dispose,在这种情况下,它只会泄漏更多内存。我已经用框架版本2.0和4.0测试过了,以前有人遇到过吗?有什么解决办法吗


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

您是否尝试过使用和
Dispose()
? 信息来源

更新 尝试调用
de.Close()在使用结束之前


很抱歉,我实际上没有活动域服务来测试此功能。

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

编辑:

我已经能够使用perfmon重新设置明显的泄漏,并在测试应用程序(Experiments.vshost for me)的进程名上添加一个专用字节计数器

私有字节计数器将在应用程序循环时稳定增长,大约从40000000开始,然后每隔几秒钟增长大约一百万字节。好消息是,当您终止应用程序时,计数器会恢复正常(35237888),因此最终会进行某种清理

我附上了perfmon泄漏时的屏幕截图

更新:

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

FindOne()命令不会泄漏内存,但我不确定如何才能使该选项对您有效,可能需要不断编辑过滤器,在我的AD控制器上,只有一个域,因此findall和FindOne会给出相同的结果

我还尝试将10000个线程池工作人员排队,以生成相同的DirectorySearcher.FindAll()。它完成的速度快了很多,但是它仍然泄漏内存,实际上私有字节增加到了80MB左右,而不是“正常”泄漏的48MB


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

托管包装器实际上不会泄漏任何内容。如果不调用
Dispose
,未使用的资源仍将在垃圾收集期间回收

但是,托管代码是基于COM的ADSI API之上的包装器,当您创建
DirectoryEntry
时,底层代码将调用。当处置
DirectoryEntry
或在完成过程中释放返回的COM对象

:

  • 此内存泄漏发生在所有版本的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中确实存在未记录的内存泄漏,否则我能给出的唯一解释就是非托管堆的碎片化。ADSIAPI由进程内COM服务器实现,每个搜索可能会在进程的非托管堆上分配一些内存。如果此内存变得碎片化,则在为新搜索分配空间时,堆可能必须增长

如果我的假设是正确的,一种选择是在单独的AppDomain中运行搜索,然后可以回收该AppDomain以卸载ADSI并回收内存。然而,尽管内存碎片可能会增加对非托管内存的需求,但我认为需要多少非托管内存是有上限的。当然,除非你有漏洞


另外,你也可以试着和他们一起玩。将其设置为
false
是否会消除泄漏?

尽管它可能很奇怪,但似乎只有在不处理搜索结果的情况下才会发生内存泄漏。按以下方式修改问题中的代码不会泄漏任何内存:

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);
  ..
}

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

找到了一种快速而肮脏的解决方法

我的程序内存增长也有类似的问题,但将.GetDirectoryEntry().Properties(“cn”).Value更改为

.GetDirectoryEntry().Properties(“cn”).Value.ToString前面带有一个if,以确保.Value不为null


我能够告诉GC.Collect以除去我的foreach中的临时值。看起来.value实际上是让对象保持活动状态,而不是让它被收集

这只是为了说明问题