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.