Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET behind the scenes: What does 'object' store?

Tags:

.net

clr

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?

like image 445
configurator Avatar asked Apr 06 '11 03:04

configurator


2 Answers

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.

like image 192
Hans Passant Avatar answered Nov 02 '22 12:11

Hans Passant


(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).

like image 5
user541686 Avatar answered Nov 02 '22 12:11

user541686