I have a piece of code that gets a list of processes on a remote computer using the static method Process.GetProcessesByName(String, String), this runs on a lot of computers (a few thousands) and I've noticed it's a cause of a major memory leak.
I ran ANTS memory profiler which told me that most of my memory is taken by strings, strings containing strage values like "% Idle Time", "Processor Information", and "Cache Faults/sec". I've recognized those strings as probably being a part of Performance Counters in the program, the problem is I don't have any performance counters in the program.
Digging deeper found out those strings are held in hashtables that are held by PerformanceCounterLib which are held by ANOTHER hashtable that is stored inside an internal static member of the PerformanceCounterLib class (which in itself is internal).
Digging even deeper into the rabbit hole, I've found out that Process.GetProcesesByName uses PerformanceCounterLib to get the process list running on a distant computer and that for each remote computer another PerformanceCounterLib instance is created and referenced in the static internal variable of PerformanceCounterLib. Each of those instances hold that hashtable of strings that I found out is clogging my memory (each of them is between 300-700 kb, meaning it's clogging up my Large Object Heap).
I did not find a way to delete those unused PerformanceCounterLib instances, they are all internal and the user has no access to them.
How can I fix my memory problem? This is REALLY bad, my program hits 5GB (my server's limit) within 24 hours.
EDIT: added a piece of code (not tested) that should reproduce the problem. For clarification:
/// 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;
}
}
WARNING: This is just a very dirty quickfix, but use reflection to kill em off.
Accessing private variables: Can I change a private readonly field in C# using reflection?
Example using static class: Using Reflection to set a static variable value before object's initialization?
You can use variations of
typeof(Process).GetFields(BindingFlags.Static | BindingFlags.NonPublic)
to find the field etc.I believe a quick fix is warrented since the behaviour of
Process
is obviously not correct.You can call PerformanceCounter.CloseSharedResources.
Internally, this calls
PerformanceCounterLib.CloseAllLibraries
, which does what it sounds like.I'd advise making sure that you call this at a time when no calls to
GetProcessesByName
are in progress, since it looks like there may be some race conditions insidePerformanceCounterLib
that you don't want to provoke.i.e. there's a shared variable called
libraryTable
that is checked once then assumed to continue to be valid in one method, and yet might be cleared byCloseAllLibraries
at any time - so its decidedly not thread safe.I was inspecting with ILSpy and analyzed the method call stack of your method. You are right, there are a static hashtable. I suggest: you should call in the
PerformanceCounter
class the following method:That calls the
PerformanceCounterLib.CloseAllLibraries();
which disposes all used hashtable.