.NET behind the scenes: what stores an “object”?

A naive type system will store objects as a pointer to its type (which contains a lot of useful information, such as vtable, size of an object, etc.), followed by its data. If .Net has such a type system, object will occupy 4 bytes on a 32-bit system and 8 bytes on a 64-bit version.

We see that this is not so. An object’s overhead is two pointer sizes, plus there is a “minimum” size for another pointer size.

So what does object really store in it, behind the scenes?

+8
clr
source share
2 answers

Yes, this is what it looks like. The "descriptor type", the so-called "method table pointer", is at offset 0, the object data is followed at offset 4. There is an additional field at offset 4 with the name "syncblock". It exists because it also participates in garbage collection when object space is not used, a double list of free blocks, which requires two pointers. Without allowing this to be wasted, the synchronizer has several uses, such as saving the lock state, saving a hash code, saving a pointer to an explicit clock when there is too much to store.

The smallest possible object for a byte byte, 4 + 4 + 1 = 9 bytes. But the distribution granularity for the GC heap is 4 bytes, so you get the next multiple of 4, 12 bytes.

This is all pretty noticeable with the debugger in Visual Studio. You will find the clues in this answer .

+7
source share

(This is all from the Microsoft CLI common source, it has the CLR source code.)

If you 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 clear. In addition, in clr\src\vm\gcscan.cpp you can see expressions such as

 _ASSERTE(g_pObjectClass->GetBaseSize() == MIN_OBJECT_SIZE); 

or

 _ASSERTE(totalSize < totalSize + MIN_OBJECT_SIZE); 

which, I think, explains why you see the unexpected size of the object. :)


Update:

@ She had a great point in the sync block; I just want to point out the subtlety once again confirmed 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! }; 

Pay attention to this part:

The index of the synchronization block is actually at a negative offset .

Thus, the synchronization block, apparently, does not actually follow the method table (as Hans mentioned), but it does reach it - so this is not the “normal” part of the object (due to the lack of a better word).

+5
source share

All Articles