Sizes of CLR objects. Precise definition

    I think many developers on managed code have always been interested in: how many bytes does an instance of an object occupy? And what is the size limit of one object in the CLR? Are there any differences in memory allocation between 32-bit and 64-bit systems? If these questions are not for you an empty phrase, then please, under cat.

    Foreword

    First, recall that in .NET there are 2 types of objects: value types and reference types , which are created, respectively, on the stack and heap (managed by the garbage collector).
    Value types are designed to store simple data, be it a number, a character. When assigning a value to a variable, each object field is copied. Also, the lifetime of such objects depends on the scope. Dimensions of value types are defined in the Common Type System and comprise:
    CTS TypeNumber of bytes
    System.Byteone
    System.SByteone
    System.Int162
    System.Int32four
    System.Int64eight
    System.UInt162
    System.UInt32four
    System.UInt64eight
    System.Singlefour
    System.Doubleeight
    System.char2
    System.Decimalsixteen

    Reference types, in contrast, are a reference to the area of ​​memory occupied by an instance of an object on the heap.

    The internal structure of CLR objects is as follows:



    For variable reference types, a fixed-size value (4 bytes, DWORD type) is placed on the stack containing the address of the object instance created in the regular heap (there is also Large Object Heap, HighFrequencyHeap, etc., but we will not focus on them). For example, in C ++, this value is called a pointer to an object, and in the .NET world, it is called a reference to an object.

    Initially, the value of SyncBlock is zero. However, the hash code of the object can be stored in SyncBlock (when calling the GetHashCode method ), or the syncblk record number , which places the environment in the object header during synchronization (usinglock , or directly Monitor.Enter ).

    Each type has its own MethodTable, and all instances of objects of the same type refer to the same MethodTable. This table stores information about the type itself (interface, abstract class, etc.).

    Reference type pointer - a reference to an object stored in a variable placed on the stack with offset 4.
    The rest are class fields.

    SOS

    We pass from theory to practice. It is not possible to set the size of an object using standard CLR tools. Yes, there is a sizeof operator in C #, but it is intended to set the size of unmanaged objects, as well as the size of value types. In questions of reference types - is useless.

    For these purposes, there is an extension of the Visual Studio debugger - SOS (Son of Strike) .

    Before starting use, you must enable unmanaged code debugging:



    To activate SOS, during debugging you need to open VS> Debug> Windows> Immediate Window and enter the following: After which we will see its successful download: SOS has a large number of commands. In our case, only the following will be needed:
    .load sos.dll






    • ! DumpStackObjects (! DSO) - displays a list of detected objects within the current stack
    • ! DumpObj (! DO) - displays information about the object at the specified address
    • ! ObjSize - Returns the full size of the object. A little later we will consider its purpose

    Other commands can be found by typing ! Help .

    To demonstrate, we will create a simple console application and write the MyExampleClass class :

    classMyExampleClass
    {
      byte ByteValue = 255;           // 1 байтsbyte SByteValue = 127;         // 1 байтchar CharValue = 'a';           // 2 байтаshort ShortValue = 128;         // 2 байтаushort UShortValue = 65000;     // 2 байтаint Int32Value = 255;           // 4 байтаuint UInt32Value = 255;         // 4 байтаlong LongValue = 512;           // 8 байтulong ULongValue = 512;         // 8 байтfloat FloatValue = 128F;        // 4 байтаdouble DoubleValue = 512D;      // 8 байтdecimal DecimalValue = 10M;     // 16 байтstring StringValue = "String";  // 4 байта
    }
    

    Take a calculator and calculate the estimated size for an instance of the class - for now, 64 bytes.

    However, remember at the beginning of the article about the structure of objects? So the final size will be:
    CLR-object = SyncBlock (4) + TypeHandle (4) + Fields (64) = 72

    Check the theory.
    Add the following code:

    classProgram
    {
      staticvoidMain(string[] args)
      {
        var myObject = new MyExampleClass();
        Console.ReadKey(); //Ставим здесь breakpoint
      }
    }
    

    And run debugging (F5).
    Let's enter the following commands in the Immediate Window: In the screenshot above, the address of the object myObject is highlighted , which we will pass as a parameter to the command! DO: Well, the size of myObject is 72 bytes. Is not it? The answer is no. The fact is that we forgot to add the string size of the StringValue variable. Its 4 bytes is just a reference. And here we will check the true size now. Let's enter the command! ObjSize: Thus, the real size of myObject is 100 bytes. An additional 28 bytes is taken by the StringValue variable. However, we will verify this. To do this, use the address of the StringValue 01b8c008 variable :

    .load sos.dll
    !DSO






















    What makes up the size of System.String?

    Firstly, in CTS, characters (type System.Char ) are represented in Unicode and occupy 2 bytes.

    Secondly, a string is nothing more than an array of characters. So in StringValue we wrote the value “String”, which is 12 bytes.

    Thirdly, System.String is a reference type, which means that it is located in the GC Heap, and will consist of SyncBlock, TypeHandle, Reference point + other fields of the class. The reference point will not be taken into account here, because already counted in the class MyExampleClass (reference 4 bytes).

    Fourth, the structure of System.String is as follows:



    Additional class fields are m_stringLength variables of type Int32 (4 bytes), m_firstChar of type Char (2 bytes), the Empty variable will not be considered, because is an empty static string.

    Also pay attention to the size - 26 bytes instead of 28, calculated earlier. Put it all together:
    StringValue = SyncBlock (4) + TypeHandle (4) + m_stringLength (4) + m_firstChar (2) + “String” (12) = 26

    An additional 2 bytes are created due to the alignment produced by the CLR memory manager.

    x86 vs. x64

    The main difference is the size of the DWORD - memory pointer. On 32-bit systems, it is 4 bytes, on 64-bit systems it is already 8 bytes.
    So, if an empty class is only 12 bytes in x86, then x64 has 24 bytes.

    CLR object size limit

    It is generally accepted that the size of System.String is limited only by the available system memory.

    However, any instance of any type cannot occupy more than 2 Gb of memory. And this limitation applies to both x86 and x64 systems.

    So, List, although it has a LongCount () method , this does not mean the ability to arrange 2 ^ 64 objects. The solution could be to use the BigArray class, designed for this purpose.

    Afterword

    In this article, I wanted to touch on the issue of finding the sizes of CLR objects. Of course, there are pitfalls, especially with the! ObjSize team, when double counting can occur due to the use of intern strings.

    For the most part, the question of the size of objects, their alignment in memory comes only when there is a strong need - the possibility of optimizing the use of resources.

    I hope the article turned out to be interesting and useful. Thanks for attention!

    useful links


    Also popular now: