我有一个漫长的运行过程,需要经常在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())
{
}
}
}
}
这些类的文档说如果没有调用Dispose(),它们将泄漏内存。我已经尝试过没有处置,在这种情况下它只会泄漏更多内存。我用框架版本2.0和4.0测试了这个以前有人遇到过这个吗?有没有解决方法?
更新:我尝试在另一个AppDomain中运行代码,它似乎也没有帮助。
答案 0 :(得分:14)
尽管可能很奇怪,但只有在您对搜索结果不做任何操作时才会发生内存泄漏。如下修改问题中的代码不会泄漏任何内存:
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);
..
}
我的应用程序中的泄漏是由于某些其他非托管缓冲区未正确释放而我所做的测试具有误导性。这个问题现在已经解决了。
答案 1 :(得分:6)
托管包装器并没有真正泄漏任何东西。如果您不调用Dispose
,则在垃圾回收期间仍会回收未使用的资源。
但是,托管代码是基于COM的ADSI API之上的包装器,当您创建DirectoryEntry
时,底层代码将调用ADsOpenObject
function。处理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中确实存在未记录的内存泄漏,否则我可以提出的唯一解释是非托管堆的碎片。 ADSI API由进程内COM服务器实现,每次搜索都可能在进程的非托管堆上分配一些内存。如果此内存碎片化,则在为新搜索分配空间时,堆可能必须增长。
如果我的假设成立,一个选项是在单独的AppDomain中运行搜索,然后可以回收以卸载ADSI并回收内存。但是,即使内存碎片可能会增加对非托管内存的需求,我也预计会有多少非托管内存需要上限。除非你有泄漏。
此外,您可以尝试使用DirectorySearcher.CacheResults
property。是否将其设置为false
可以消除泄漏?
答案 2 :(得分:3)
由于实施限制,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),因此最终会进行某种清理。我附上了一个屏幕截图,显示了当它在泄漏
时的样子更新
我尝试了一些解决方法,例如禁用DirectoryServer对象上的缓存,但它没有帮助。
FindOne()命令不会泄漏内存,但我不知道你要做什么才能使该选项适合你,可能在我的AD控制器上不断编辑过滤器,只有一个域名,所以findall&找到同样的结果。
我还尝试排队10,000个线程池工作者来制作相同的DirectorySearcher.FindAll()。它完成得更快,但它仍然泄漏内存,实际上私有字节上升到大约80MB,而不是仅仅48MB的“正常”泄漏。
因此,对于此问题,如果您可以让FindOne()为您工作,那么您有一个解决方法。祝你好运!
答案 3 :(得分:2)
答案 4 :(得分:0)
找到了一个快速而肮脏的方法。
我的程序中存在类似的内存增长问题,但更改了 .GetDirectoryEntry()。属性(“cn”)。值到
.GetDirectoryEntry()。属性(“cn”)。Value.ToString 带有if before hand以确保.value不为null
我能够告诉GC.Collect摆脱我的foreach中的临时值。看起来.value实际上是保持对象存活而不是允许它被收集。