Skip to content

Instantly share code, notes, and snippets.

@airbreather
Last active July 16, 2018 12:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save airbreather/42e0f54491b719de62a716467ac1ac45 to your computer and use it in GitHub Desktop.
Save airbreather/42e0f54491b719de62a716467ac1ac45 to your computer and use it in GitHub Desktop.
Does the .NET garbage collector REALLY move tenured objects around? I mean, REALLY?
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net471</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
using System;
using System.Runtime;
using System.Runtime.CompilerServices;
static class Program
{
// for more fun, try setting this to:
// - really low values, like 1 or 50 or so
// - the neighborhood of 1050
// - big enough for it to go on the LOH (84976 on x64)
const int Length = 1300;
static byte[] arr1 = new byte[Length];
static byte[] arr2 = new byte[Length];
static byte[] arr3 = new byte[Length];
[MethodImpl(MethodImplOptions.NoOptimization)]
static unsafe void Main()
{
// if our arrays are big enough to be allocated on the LOH, then we'll
// need to specify the flag that says to compact it.
bool onLargeObjectHeap = GC.GetGeneration(new byte[Length]) == GC.MaxGeneration;
// set it up so that the second digit of each byte value says which
// array it came from. this will sometimes give us a little insight
// into what the GC is doing (e.g., when Length is anywhere between
// 1200 or so and just under the LOH threshold, on .NET Fat).
for (int i = 0; i < Length; i++)
{
arr1[i] = 111;
}
for (int i = 0; i < Length; i++)
{
arr2[i] = 121;
}
for (int i = 0; i < Length; i++)
{
arr3[i] = 131;
}
// tenure all the arrays so that they move around as rarely as possible
while (GC.GetGeneration(arr1) != GC.MaxGeneration ||
GC.GetGeneration(arr2) != GC.MaxGeneration ||
GC.GetGeneration(arr3) != GC.MaxGeneration)
{
GC.Collect();
}
// grab a pointer to the first element of arr2.
byte correctValue;
byte* pArr2;
fixed (byte* pArr2Fixed = arr2)
{
pArr2 = pArr2Fixed;
correctValue = pArr2[0];
}
// at this point, people will tell you that the GC can legally move
// arr2 so that pArr2 no longer points to the first element of arr2.
// let's test to see if it actually does that.
for (ulong cnt = 1; cnt != ulong.MaxValue; ++cnt)
{
byte curr = pArr2[0];
if (curr != correctValue)
{
Console.WriteLine("After {0} read(s), pArr2[0] was {1} (expected {2})", cnt, curr, correctValue);
return;
}
arr1 = new byte[Length];
if (onLargeObjectHeap)
{
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
}
GC.Collect();
}
Console.WriteLine("We did a LOT of reads, and pArr2[0] was always {0}. Congratulations!", correctValue);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment