C# Process.getProcessByName(字符串,字符串)内存泄漏

C# Process.getProcessByName(字符串,字符串)内存泄漏,c#,performance,memory,memory-leaks,profiling,C#,Performance,Memory,Memory Leaks,Profiling,我有一段代码,它使用静态方法获取远程计算机上的进程列表,这在很多计算机(几千台)上运行,我注意到这是导致内存严重泄漏的原因 我运行了ANTS memory profiler,它告诉我,我的大部分内存都是由字符串占用的,这些字符串包含诸如“%Idle Time”、“Processor Information”和“Cache Faults/sec”等strage值。我已经认识到这些字符串可能是程序中性能计数器的一部分,问题是程序中没有任何性能计数器 深入挖掘发现,这些字符串保存在由Performan

我有一段代码,它使用静态方法获取远程计算机上的进程列表,这在很多计算机(几千台)上运行,我注意到这是导致内存严重泄漏的原因

我运行了ANTS memory profiler,它告诉我,我的大部分内存都是由字符串占用的,这些字符串包含诸如“%Idle Time”、“Processor Information”和“Cache Faults/sec”等strage值。我已经认识到这些字符串可能是程序中性能计数器的一部分,问题是程序中没有任何性能计数器

深入挖掘发现,这些字符串保存在由PerformanceCounterLib保存的哈希表中,而PerformanceCounterLib保存在另一个哈希表中,该哈希表存储在PerformanceCounterLib类的内部静态成员(其本身是内部的)中

深入兔子洞,我发现Process.getProcessbyname使用PerformanceCounterLib获取远程计算机上运行的进程列表,并且为每个远程计算机创建另一个PerformanceCounterLib实例,并在PerformanceCounterLib的静态内部变量中引用。这些实例中的每一个都保存着我发现正在阻塞我的内存的字符串哈希表(每个都在300-700KB之间,这意味着它正在阻塞我的大对象堆)

我没有找到删除那些未使用的PerformanceCounterLib实例的方法,它们都是内部的,用户无权访问它们

如何修复我的内存问题?这真的很糟糕,我的程序在24小时内达到5GB(我的服务器限制)

编辑:添加了一段代码(未测试),可以重现问题。请澄清:

/// computerNames is a list of computers that you have access to
public List<string> GetProcessesOnAllComputers(List<string> computerNames)
{
    var result = new List<string>();
    foreach(string compName in computernames)
    {
        Process[] processes = Process.GetProcesses(compName); // Happens with every     method that gets processes on a remote computer
        string processString = processes.Aggregate(new StringBuilder(), (sb,s) => sb.Append(';').Append(s), sb => sb.ToString());
        result.Add(processString);
        foreach (var p in processes)
        {
            p.Close();
            p.Dispose();
        }
        processes = null;
    }
}
///computerNames是您可以访问的计算机的列表
public List GetProcessesOnAllComputers(列出计算机名称)
{
var result=新列表();
foreach(ComputerName中的字符串compName)
{
Process[]processs=Process.getprocesss(compName);//在远程计算机上获取进程的每个方法都会发生这种情况
string processString=processs.Aggregate(新的StringBuilder(),(sb,s)=>sb.Append(“;”).Append(s),sb=>sb.ToString());
添加(processString);
foreach(过程中的var p)
{
p、 Close();
p、 处置();
}
进程=空;
}
}

警告:这只是一个非常肮脏的快速修复程序,但使用反射来杀死它们

访问私有变量:

使用静态类的示例:

您可以使用
typeof(Process).GetFields(BindingFlags.Static | BindingFlags.NonPublic)
的变体来查找字段等

我认为快速修复是值得的,因为
过程的行为显然是不正确的。

您可以调用

在内部,这将调用
PerformanceCounterLib.CloseAllLibraries
,它会执行听起来像的操作

我建议您确保在没有调用
GetProcessesByName
的时候调用此函数,因为在
PerformanceCounterLib
中可能存在一些您不想引起的竞争条件


i、 e.有一个名为
libraryTable
的共享变量,该变量被检查一次,然后假设在一个方法中继续有效,但可能会在任何时候被
CloseAllLibraries
清除-因此它肯定不是线程安全的。

我正在使用ILSpy检查并分析您的方法的方法调用堆栈。 你是对的,这里有一个静态哈希表。 我建议:您应该在
PerformanceCounter
类中调用以下方法:

// System.Diagnostics.PerformanceCounter
/// <summary>Frees the performance counter library shared state allocated by the counters.</summary>
/// <filterpriority>2</filterpriority>
/// <PermissionSet>
///   <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1">
///     <Machine name=".">
///       <Category name="*" access="Browse" />
///     </Machine>
///   </IPermission>
/// </PermissionSet>
public static void CloseSharedResources()
{
    PerformanceCounterPermission performanceCounterPermission = new PerformanceCounterPermission(PerformanceCounterPermissionAccess.Browse, ".", "*");
    performanceCounterPermission.Demand();
    PerformanceCounterLib.CloseAllLibraries();
}
//System.Diagnostics.PerformanceCounter
///释放由计数器分配的性能计数器库共享状态。
/// 2
/// 
///   
///     
///       
///     
///   
/// 
公共静态void CloseSharedResources()
{
PerformanceCounterPermission PerformanceCounterPermission=新的PerformanceCounterPermission(PerformanceCounterPermissionAccess.Browse,“.”,“*”;
performanceCounterPermission.Demand();
PerformanceCounterLib.CloseAllLibraries();
}

调用
PerformanceCounterLib.CloseAllLibraries()它处理所有使用过的哈希表。

如果我理解正确,每个远程机器会额外占用300-700 kB(我猜是字节还是位?)的空间,并且您无论如何都不能释放它,因为它是由进程静态类持有的?您是否尝试过使用
EnterDebugMode
LeaveDebugMode
方法?我在考虑以某种方式“重置”进程静态类。但是正如上面的评论所说,也要提交一个bug,因为这显然不是想要的行为。很容易错过Process.Dispose()方法。从技术上讲,还可以使用Process类进行大量工作,并且永远不会消耗足够的GC堆来触发收集。终结器线程上的死锁很糟糕。首先从这里开始,看看.NET性能计数器(呵呵)。@HansPassant我肯定已经关闭并处理了进程阵列。这是我注意到的代码中的第一个错误,它可能确实减轻了负载,但事实并非如此。我想到了这一点,如果我找不到任何更干净的修复程序,我会这么做。