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

Can*_*cer 25 .net c# directoryservices memory-leaks adsi

我有一个漫长的运行过程,需要经常在Active Directory上进行大量查询.为此,我一直在使用Directory.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())
            {
            }            
         }
    }
}
Run Code Online (Sandbox Code Playgroud)

这些类的文档说如果没有调用Dispose(),它们将泄漏内存.我试过没有处理过,它只是泄漏了更多的内存.我用框架版本2.0和4.0测试了这个以前有人遇到过这个吗?有没有解决方法?

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

Can*_*cer 15

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

using (var src = mySearcher.FindAll())
{
   var enumerator = src.GetEnumerator();
   enumerator.MoveNext();
}
Run Code Online (Sandbox Code Playgroud)

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

internal UnsafeNativeMethods.IDirectorySearch SearchObject
{
    get
    {
        if (this.searchObject == null)
        {
            this.searchObject = (UnsafeNativeMethods.IDirectorySearch) this.rootEntry.AdsObject;
        }
        return this.searchObject;
    }
}
Run Code Online (Sandbox Code Playgroud)

除非初始化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;
        }
    ..
   }
}
Run Code Online (Sandbox Code Playgroud)

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

public bool MoveNext()
{
  ..
  int firstRow = this.results.SearchObject.GetFirstRow(this.results.Handle);
  ..
}
Run Code Online (Sandbox Code Playgroud)

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


Mar*_*age 6

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

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

将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消除泄漏?