【问题标题】:Process.GetProcessesByName(String, String) Memory LeakProcess.GetProcessesByName(String, String) 内存泄漏
【发布时间】:2012-10-16 13:56:31
【问题描述】:

我有一段代码使用静态方法Process.GetProcessesByName(String, String) 获取远程计算机上的进程列表,它在很多计算机(几千台)上运行,我注意到这是一个主要的原因内存泄漏。

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

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

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

我没有找到删除那些未使用的 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;
    }
}

【问题讨论】:

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

标签: c# performance memory memory-leaks profiling


【解决方案1】:

您可以拨打PerformanceCounter.CloseSharedResources

在内部,这会调用PerformanceCounterLib.CloseAllLibraries,就像听起来一样。

我建议您确保在没有对GetProcessesByName 的调用正在进行时调用它,因为看起来PerformanceCounterLib 内部可能存在一些您不想激怒的竞争条件。

即有一个名为 libraryTable 的共享变量被检查一次,然后假定它在一种方法中继续有效,但可能随时被 CloseAllLibraries 清除 - 所以它绝对不是线程安全的。

【讨论】:

    【解决方案2】:

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

    访问私有变量: Can I change a private readonly field in C# using reflection?

    使用静态类的示例: Using Reflection to set a static variable value before object's initialization?

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

    我认为需要快速修复,因为 Process 的行为显然不正确。

    【讨论】:

    • 我已经想到了,如果我找不到任何更干净的修复程序,我会这样做。
    【解决方案3】:

    我正在使用 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();
    }
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多