C# 使用DirectorySearcher.FindAll()时内存泄漏
我有一个长时间运行的过程,需要经常在Active Directory上执行大量查询。为此,我一直在使用System.DirectoryServices命名空间,使用DirectorySearcher和DirectoryEntry类。我注意到应用程序中存在内存泄漏 可使用以下代码复制: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
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实际上是让对象保持活动状态,而不是让它被收集 这只是为了说明问题