.NET behind the scenes: What does 'object' store?
A naïve type system would store objects as a pointer开发者_开发百科 to its type (which contains lots of useful information, like a vtable, object size, etc.) followed by its data. If .Net had such a type system an object
would take up 4 bytes on a 32-bit system, and 8 bytes on 64-bit.
We can see that it doesn't. The object overhead is two pointer-sizes, plus, there's a 'minimum' size of one more pointer-size.
So what does object
actually store in it, behind the scenes?
Yes, that's what it looks like. The 'type handle', aka 'method table pointer' is at offset 0, the object data follows at offset 4. There's an extra field at offset-4 named the 'syncblock'. It is there because it also participates in the garbage collected heap when the object space is not in use, a double-linked list of free blocks that requires two pointers. Not letting that going to waste, the syncblock has several uses like storing the lock state, storing the hash code, storing a pointer to an explicit syncblock when too much needs to be stored.
The smallest possible object is for a boxed byte, 4 + 4 + 1 = 9 bytes. But allocation granularity for the GC heap is 4 bytes so you'll get the next multiple of 4, 12 bytes.
This is all pretty visible with the debugger in Visual Studio. You'll find hints in this answer.
(This is all from the Microsoft Shared Source CLI; it has the source code of the CLR.)
If you take a look at clr\src\vm\object.h
, you will see:
// The generational GC requires that every object be at least 12 bytes in size.
#define MIN_OBJECT_SIZE (2*sizeof(BYTE*) + sizeof(ObjHeader))
which is pretty self-explanatory. Furthermore, in clr\src\vm\gcscan.cpp
, you can see statements such as
_ASSERTE(g_pObjectClass->GetBaseSize() == MIN_OBJECT_SIZE);
or
_ASSERTE(totalSize < totalSize + MIN_OBJECT_SIZE);
which I think explains why you're seeing the unexpected object sizes. :)
Update:
@Hans had a great point on the sync block; I just want to point out a subtlety, documented again in object.h
:
/* Object
*
* This is the underlying base on which objects are built. The MethodTable
* pointer and the sync block index live here. The sync block index is actually
* at a negative offset to the instance. See syncblk.h for details.
*/
class Object
{
protected:
MethodTable* m_pMethTab;
//No other fields shown here!
};
Notice this part:
The sync block index is actually at a negative offset to the instance.
So the sync block apparently doesn't actually follow the method table (as Hans mentioned), but it comes before it -- so it's not a "normal" part of the object (for the lack of a better word).
精彩评论