I believe the header size is two words - one for the type reference and one for the sync block and other flags. The padding is (I believe) just enough to round the total size up to a whole number of words.
For instance, a reference type with just an "int" in takes 12 bytes on x86, as demonstrated here:
using System;
public class Foo
{
int x;
public Foo(int x)
{
this.x = x;
}
}
public class Test
{
static void Main(string[] args)
{
int length = int.Parse(args[0]);
Foo x = new Foo(0);
Foo[] array = new Foo[length];
// Make sure that JITting the string constructor doesn't
// change things
long start = GC.GetTotalMemory(true);
for (int i=0; i < length; i++)
{
array[i] = new Foo(i);
}
long end = GC.GetTotalMemory(true);
GC.KeepAlive(array);
GC.KeepAlive(x);
decimal totalDecimal = end-start;
Console.WriteLine(totalDecimal / length);
}
}
One interesting point - for some reason an instance of System.Object takes 12 bytes (on x86) instead of the 8 that I would otherwise have predicted. It's as if the minimum size is 12 bytes, but you get the first four bytes of real data free :)
I don't know why the size reported isn't exactly an integer, btw - I suspect it's something to do with a little bit of extra memory required for per page in the managed heap, or something like that. Sometimes the result is a little bit over 12, sometimes a little bit under 12 - that seems to depend on the length given. (The previous version of this answer had a bug in, where it would parse the first command line arg but then ignore it. I've fixed that.) Anyway, I don't believe this slight inaccuracy has anything to do with the size of an individual object in memory.