I am creating new byte arrays which are not being collected by GC and are living in memory and increasing the private bytes. The code below gets executed every 10 seconds. How do I explicitly clear the variable after I am done with it?
byte[] outputMessage = new byte[10000];
//Do some work here
How do you know they are not being collected? The code you provide is fine, and it should become eligible for collection if you don't have any dangling references to it.
Explicitly, you can clear a reference by
outputMessage = null;
and you can hint the GC that it should do its job with this:
GC.Collect();
However, there's no guarantee that your object will get collected, and manually messing with the GC is usually not needed.
If you are adding a new event handler all the time, are you deleting any old ones? If not, then they are still keeping the reference for you.
EDIT: for clarity, and for new info by OP
Ensure that you have no references to your array. Examine that you have not an assignment to another variable that keeps the array in memory.
Do you leave the focus of your outputMessage?
- If it is declared inside a method: Do you leave it or do you have some (intendet) endless loop and remain in it?
- If it is declared global inside a class object: Does your complete class remain in memory by a reference to this?
It's hard to say for sure without seeing the context that thats used in. Unless you are keeping around references to each outputMessage they will get garbage collected eventually whenever the GC decides to run.
What were you looking at to see that the private bytes kept growing and would never shrink? Also were you only running with the debugger attached or were you running in release?
Do you really need to create a new array every 10 seconds? It might be faster to simply Array.Clear and reuse it.
Consider this: Is it possible to encapsulate your byte[] in a class that implements IDisposable? After use, you can dispose your object by explicitly calling Dispose(). I'm not sure if this guides you to the solution, but it would be my next attempt.
When a managed array leaves scope it gets flagged for garbage collection. If the array is of a value type the deallocation of items is quick but doesn't happen until the array gets collected. Remember, byte
is a value type but byte[]
is a reference type.
Here's a quick sample that illustrates:
void Main()
{
const int LOOPS = 5;
{
Console.WriteLine("When the arrays are kept inside the method's scope:");
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (starting memory)", GC.GetTotalMemory(false)));
for(int i = 0; i < LOOPS; i++)
this.AllocateArray(i);
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (exited local scope)", GC.GetTotalMemory(false)));
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (after GC collection ran)", GC.GetTotalMemory(true)));
Console.WriteLine("\nWhen the arrays are outside the method's scope:");
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (starting memory)", GC.GetTotalMemory(false)));
var arrays = new byte[LOOPS][];
for(int i = 0; i < LOOPS; i++)
this.AllocateArray(i, arrays);
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (exited local scope)", GC.GetTotalMemory(false)));
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (after GC collection ran)", GC.GetTotalMemory(true)));
arrays[0][0] = 1; // Prevent the arrays from being optimized away
}
Console.WriteLine("\nAll scopes exited:");
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (before GC runs)", GC.GetTotalMemory(false)));
Console.WriteLine(String.Format(" GC Memory: {0:N0} bytes (after GC collection ran)", GC.GetTotalMemory(true)));
}
public void AllocateArray(int run)
{
var array = new byte[20000000];
Thread.Sleep(100); // Simulate work..
Console.WriteLine(String.Format("[{0}] GC Memory: {1:N0} bytes (local array allocated)", run+1, GC.GetTotalMemory(false)));
array[0] = 1; // Prevent the array from being optimized away
}
public void AllocateArray(int run, byte[][] arrays)
{
arrays[run] = new byte[20000000];
Thread.Sleep(100); // Simulate work..
Console.WriteLine(String.Format("[{0}] GC Memory: {1:N0} bytes (array allocated)", run+1, GC.GetTotalMemory(false)));
}
The output from this looks something like this (exact results will vary):
When the arrays are kept inside the method's scope:
GC Memory: 24,576,232 bytes (starting memory)
[1] GC Memory: 45,002,324 bytes (local array allocated)
[2] GC Memory: 44,845,548 bytes (local array allocated)
[3] GC Memory: 64,574,296 bytes (local array allocated)
[4] GC Memory: 64,959,472 bytes (local array allocated)
[5] GC Memory: 44,675,340 bytes (local array allocated)
GC Memory: 44,675,340 bytes (exited local scope)
GC Memory: 24,347,296 bytes (after GC collection ran)
When the arrays are outside the method's scope:
GC Memory: 24,355,488 bytes (starting memory)
[1] GC Memory: 44,467,612 bytes (array allocated)
[2] GC Memory: 64,681,980 bytes (array allocated)
[3] GC Memory: 85,493,004 bytes (array allocated)
[4] GC Memory: 104,442,028 bytes (array allocated)
[5] GC Memory: 124,450,236 bytes (array allocated)
GC Memory: 124,450,236 bytes (exited local scope)
GC Memory: 124,357,588 bytes (after GC collection ran)
All scopes exited:
GC Memory: 124,365,780 bytes (before GC runs)
GC Memory: 24,356,996 bytes (after GC collection ran)
To solve your issue:
- Make sure you aren't hanging on to any references to the
byte[]
s.
The bytes won't get cleared until all references to the array are
gone and it's completely out of scope.
- Explicitly call
GC.Collect()
after you've left the scope of the byte array. Once
it's out of scope, the byte[]
will clear on its own but you can
tell it to run instead of waiting.
This test case only works in release mode.
I am creating an array using a guid class (it's easy). It would have 16 elements.
[TestMethod]
public void ByteArrayReleasesMemoryWhenTheyGoOutOfScope()
{
// *** WORKS ONLY IN RELEASE MODE ***
// arrange
var weakRef = new WeakReference(null);
// easy way to generate byte array
var byteArray = Guid.NewGuid().ToByteArray();
weakRef.Target = byteArray;
// act
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
// assert
Assert.IsFalse(weakRef.IsAlive);
}