.Net Type Internals


 In this post:
          ·        Introduction
          ·        .Net managed environment
          ·        Virtual Address Space
          ·        Reference Type internals
o   Reference Type - memory layout
o   Object Header Word \ Sync Block Index (SBI)
o   Type Object Pointer (TOP) \ Method Table Pointer (MT)
o   Loader Heap
o   Example
o   SOS DLL debugger extension
o   NGEN - Native Image Generator
          ·        Value Type internals
o   Value Type - memory layout
o   Boxing
o   Example
    ·        Summary



Introduction

In this post I'll describe how the .Net types: Reference types & Value types are represented in the application's memory.

Understanding the internals of a .Net process, by examining topics such as:
          -        .Net types memory layout
          -        JIT Compiler (Jitter)
          -        Execution model
 would result in best practices and a proper use of those types, which would affect directly on the application's overall performance.


As every .Net developer knows there are several differences between Reference Types & Value Types on the programming level:

          -        Comparison ('=='):
o   Ref. type: The reference is compered.
o   Value type: The content is compered (meaning all fields).
          -        Sending a variable\object to a method:
o   Ref. type: Call by reference.
o   Value type: Call by value.
          -        Assignment:
o   Ref. type: The reference is copied.
o   Value type: The entire content is copied.


·        Reference types: classes, interfaces, delegates and arrays.
·        Value types: primitive types (int, float, decimal etc'), enumerations (enums) and structs.


While examining the .Net type's memory layout, meaning how and where they reside in the application's memory? How the CLR address them? We could write better performance oriented programs, and save a lot of work to the Garbage Collector (GC) algorithm, a topic I'll address in a another post.

Remark:
At the time of writing this post the latest CLR version was 4.5.


.Net managed environment

The .Net framework (CLR & FCL) provide managed environment, meaning we don’t need to manually (by code) free memory of unused objects, the GC performs all the necessary, in the most effective way. 

         -        CLR – Common Language Runtime.
         -        FCL – Framework Class Library.


In addition, the memory allocated for each .Net process is divided into several sections\segments by the CLR, for this article describing the reference type and value type internals; we'll focus on the following sections:

            1.     Managed Heap (a.k.a GC Heap)
            2.     Thread Stack


Remark:
As aforementioned, the process's memory is divided into additional memory segments (for different purposes), such as: the loader heap (High-Frequency Heap, Low-Frequency Heap), Large Object Heap (LOH), JIT code heap (contains the JIT compiler instructions) etc.

I'll address some of these segments in this article, and elaborate on other in different posts.



Remark: Virtual Address Space

Prior to diving into the .Net reference type internals, I'd like to elaborate a bit about Virtual Address Space, with a distinction between 32bit & 64bit Win OS.

A Virtual Address Space is the set of ranges of virtual addresses that a process could use while executing, provided & managed by the operating system.

On a 32-bit architecture:
When running a process on a 32bit system, the OS assign 4GB of memory for its virtual address space (232).
However, by default, the OS allows the process to use only 2GB of memory (user-mode), while the other 2GB are used by the OS itself (kernel-mode).

On some Win 32bit system, it’s possible to enlarge this virtual address space to 3GB by setting the IMAGE_FILE_LARGE_ADDRESS_AWARE flag.

On a 64-bit architecture:
As is known, on a 64bit system, we could run both 32bit and 64bit processes.
           -        32bit process: up to 4GB.
           -        64bit process: up to 8TB (and more…).

As this is not the main topic of this article, I'm not going to elaborate additionally on this, just mention that we could also control these extensions by using the LARGEADDRESSAWARE flag.

 But it's very important to understand that the OS is controlling and assigning the virtual address space for the process, the relevant sizes for different OS versions and processes, since it impacts on our application's behavior, and its overall performance.




Reference Type internals

Reference types are allocated in the Heap memory, which is managed by the Garbage Collector (GC).
Large objects are allocated in the Large Object Heap (LOH).

Allocation: (new objects)
The allocation operation is relatively cheap operation, meaning it usually involves incrementing the 'next object pointer' (NOP) and not using a complex algorithm, which cause high performance issues.
On a multiprocessor system, there is additional synchronization performance; however, the allocation is still relatively cheap operation.

De-allocation: (old objects)
The GC 'releases' old objects by reclaiming their memory from the heap, after they are no longer needed in the application.
The GC operation is relatively high performance operation (higher than the allocation), since it's algorithm is more complex, and includes the Mark, Sweep & Compact phases.

Remark: I'll address the GC different phases in another post and describe the .Net GC algorithm – Tracing GC mechanism with relevant examples, however in short:

-         Mark Phase: The GC looks for all the live objects that are still referenced by the application. (using GC Roots)
-        Sweep Phase: The GC takes back the space occupied by dead objects.
-        Compact Phase: The GC moves live objects in memory, so that they remain consecutive in memory.



Reference Type - memory layout

When we create an instance of a .Net object with the desired properties, variables and methods, its representation in the memory also contains additional overhead, the following 2 fields:

            1.     Object Header Word (a.k.a Sync Block Index - SBI)
            2.     Type Object Pointer -TOP (a.k.a Method Table Pointer – MT Pointer)

Meaning, in addition to the data and functionality declared (by the developer) in the reference type, the .Net framework (the Compiler and the CLR) adds these additional fields (and other internal data structures and table lookups), in order to perform all the necessary runtime operations. (Dynamic Dispatch) 
These fields are being used by the JIT (just-in-time) Compiler and the CLR at runtime.



Object Header Word \ Sync Block Index (SBI)

The Sync Bloc Index field is being used for several different purposes:

            1.      Thread Synchronization (using the CLR Monitor Mechanism with the lock keyword).
            2.      To store the object's Hash Code. (used in a hash-based collection,  such as the Dictionary)
            3.      As part of the GC work.
            4.      Finalization.

In addition to the data above, the CLR uses few bits in the Sync Block Index field, in order to determine which information is currently stored in it.

Remark: For additional detailed information, with relevant examples, please review another article I published: SyncBlock Index (SBI) \ Object Header Word



Type Object Pointer (TOP) \ Method Table Pointer (MT)

The Type Object Pointer points to the object's Method Table, which is a CLR internal data structure that comprises data on the Type, and additional relevant information.

Part of the information that could be found in the Method Table is: the addresses of every method of the type, including any virtual methods of its base types, a pointer to the base class's Method Table, a pointer to its module and a pointer to its EEClass.

I'll elaborate on the Method Table in another post, just would like to emphasize an important pointer that reside in the Method Table, which is the EEClass (Execution Engine class).

The EEClass is another CLR internal data structure that comprises additional information on the type, such as: the location of static fields, reflection at runtime etc.

The information stored in the MT is used more frequently than the information in the EEClass, however both of these data structures contain all the information needed by the CLR to perform all the runtime operations and dynamic dispatch, such as: direct to the base type methods, dispatch virtual method calls, dispatch interface method calls, examine a type at runtime, access static variables etc.


Loader Heap
Both of these data structures are allocated in the Loader Heap.
The Loader Heap is an area in the process's memory used to allocate the CLR internal data structures, which live for the entire lifetime of the domain.

As opposed to the Managed Heap (an area used to allocate the application reference types), the Loader Heap is not managed by the Garbage Collector (GC).

The loader heap is divided into: HighFrequencyHeap & LowFrequencyHeap.

Data structures, such as: Method Tables, Method Descriptors and Field Descriptors are frequently accessed, thus allocated in the HighFrequencyHeap.
Data structures, such as: EEClass are not frequently accessed, thus allocated in the LowFrequencyHeap.

    
      

Example:

In this example I'll demonstrate how a simple class (with only one property) is represented in the Managed Heap, and we'll actually observe the values of the SBI and MT Pointer in the heap, using Visual Studio debug->memory window, and perform some debugging using the SOS.dll extension in the debug->Immediate window.


1.     Create a Person class:
In order to present the Reference Type memory layout, and to present the SBI and MT Pointer fields, I wrote the following 'Person' class:

public class Person
{
    public int Id { get; set; }
}

In a 32bit system, it seems as if the Person class above should consume only 4 bytes of memory, since we only have 1 integer field in this class.

However, as described above we have additional overhead, the Sync Block Index (SBI), and the Method Table Pointer (MT Pointer), which consumes additional 4 byte each.

So in this example the Person size is:

Field\Size
32bit system
64bit system
Int32 Id
4 bytes
8 bytes
SBI
4 bytes
8 bytes
MT Pointer
4 bytes
8 bytes



Total size
12 bytes
24 bytes



Person instance memory layout

The following image describes a person instance layout in the Managed Heap. (In a 32bit system)
When we create an instance of an object, we are creating a reference to that object, which points to the beginning of the Type Object Pointer (a.k.a MT Pointer), whereas the Object Word Header (a.k.a Sync Block Index) is laid in a negative offset from that object.





2.     Create 2 Person objects:
Next, I'll create 2 instances of the person class and assign an Id for each:

private static void ReferenceTypeMemoryLayoutExample()
{
    Person person1 = new Person();
    person1.Id = 3;

    Person person2 = new Person();
    person2.Id = 5;
}



3.     Debugging in Visual Studio:
I'll demonstrate this using the Visual Studio different debug windows:
-        Disassembly window – to show the assembly calls and the relevant registers.
-        Registers window – to retrieve the relevant memory addresses.
-        Memory window – to show the actual values stored in the managed heap memory.
-        Immediate window – to use the SOS DLL, to perform debugging and present the MT and other CLR data structures.


3.1  Disassembly window

As we could see in the assembly code, when creating a new instance of the person1 object, the compiler uses the ECX register to store the new instance in the heap memory.






3.2  Registers window & Memory window

So I copied the ECX register value to the address textbox in the memory window:





3.3  Memory window

Now we could see the actual values of our Person objects properties (Id) and the SBI and MT Pointer values.
Meaning the entire information of each person instance as it's being represented in the GC (managed) Heap.




In the image above we could see our objects: person1 and person2 with their values as they are stored in the managed heap.

person1
            -        The first object (id = 3) memory layout comprises the SBI 4 bytes as the first field (not used in this example, thus all 4 bytes are zeros).
            -        The next filed is the MT pointer, also represented with 4 bytes.
            -        And the last filed (the only property in our Person class example) is the Id, and we could clearly see its value is 3.

person2
            -        The second object (id = 5) memory layout also comprises the SBI 4 bytes.
            -        The next filed is the MT pointer, which we could see that in both instances of Person  is the same value (003b3458), since they have the same Method Table of the Person class.
           -        And the last field is the Id with the value 5, as we declared in the example above.



To conclude the example up to now:

We used a simple class with only one property to prove that each person instance memory layout also comprise the additional SBI and MT Pointer fields.
In addition, we saw that each person instance have the same value for the MT Pointer (Type Object Pointer), since they both of type Person, and the CLR manages the same Method Table (which is a CLR data structure) for each person instance.




3.4  Immediate window

In the immediate window I'll use the SOS.DLL extension to present all of the above (and more), by debugging our session and examining the relevant Objects, Method Tables, the Sync Block Index and other relevant CLR data.

SOS.DLL – Son of Strike

The SOS DLL is a debugger extension, provided as part of the .Net Framework (%windir%\Microsoft.NET\Framework\v4.0.30319), and could be used to perform debugging on .Net processes, and to display relevant information on the CLR internals.

Normally, I would use it in the WinDbg tool (provided as part of the Debugging Tools for Windows tool set), but for this example of exploring the reference type memory layout, I'll use the SOS.DLL inside Visual Studio in order to present the relevant information.

Remarks:
-        I'll add other posts to demonstrate how to use the SOS extension in visual studio and in WinDbg, for different examples with relevant commands.
-        There are additional debugger extensions that I'll probably describe in other debugging posts, such as: sosex.dll, psscor2.dll, psscor4.dll, ClrMD.



I've performed the following commands:

           1.      Load the SOS DLL:
-        .load sos.dll
-        Output snippet:

.load sos.dll

extension C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded


           2.      Managed Heap statistics:
            Review the current heap statistics, meaning the total objects in the heap, their types, how many were instantiated from each type, their sizes, and the Method Table addresses.
-        !DumpHeap –stat
-        Output snippet:

!DumpHeap –stat

total 6374 objects
Statistics:
      MT    Count    TotalSize Class Name
67a221cc        1           12 System.RuntimeTypeHandle
67a1fe50        1           12 System.__Filters
67a1fe00        1           12 System.Reflection.Missing
67a1fd08        1           12 System.RuntimeType+TypeCacheQueue

   67a0c3e4        2           24 System.Security.Policy.Url
   679f6f0c        2           24 System.OrdinalComparer
   003b3458        2           24 DotNetTypeInternals.BOs.Person
67a213f4        1           28 System.SharedStatics

Total 6374 objects

As we could see from the output above the Person MT address is: 003b3458 which we already saw in the memory window above.


           3.      Review the Method Table of the Person class:
            Using MT address, I could use the !DumpMT command and the –md switch to present also the method descriptor.
-        !DumpMT -md 003b3458D
-        Output:

!DumpMT -md 003b3458

EEClass: 003b193c
Module: 003b2f2c
Name: DotNetTypeInternals.BOs.Person
mdToken: 02000004  (c:\users\yonatan\documents\visual studio 2015\Projects\DotNetTypeInternals\DotNetTypeInternals\bin\Debug\DotNetTypeInternals.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 8
--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
67976a90   677f494c   PreJIT System.Object.ToString()
67976ab0   677f4954   PreJIT System.Object.Equals(System.Object)
67976b20   677f4984   PreJIT System.Object.GetHashCode()
679e7700   677f49a8   PreJIT System.Object.Finalize()
003bc060   003b344c      JIT DotNetTypeInternals.BOs.Person..ctor()
003bc048   003b341c     NONE DotNetTypeInternals.BOs.Person.get_Id()
003bc050   003b342c      JIT DotNetTypeInternals.BOs.Person.set_Id(Int32)
003bc058   003b343c     NONE DotNetTypeInternals.BOs.Person.Print()


As we could see from the output of !DumpMT example above, the Method Table data structure contains information about the Person's methods, and its inherited methods, in this case from its base class System.Object. (ToString(), Equals(..) etc.)

In the Method Description table above we could find the methods code addresses, their method descriptor and another rather interesting column: JIT.

The JIT column could be set with one of the following values:
o   PreJIT – The method was compiled using NGEN.
o   JIT – The method was compiled at runtime by the CLR JIT compiler.
o   None – The method wasn't compiled yet.


Remark: NGEN
The NGEN - Native Image Generator is a tool (provided as part of the .Net Framework) that improves .Net applications performance by creating native images that were compiled into machine code of a specific processor.
The runtime could use these native images from the native image cache on the local computer, instead of using the JIT compiler.


          4.      Review the Person constructor:
           We could use a Method Descriptor address from the !DumpMT command output and view a particular method descriptor data.
For this example I'll review the Person class's instance constructor method descriptor.

-        !DumpMD 003b344c
-        Output:

!DumpMD 003b344c

Method Name: DotNetTypeInternals.BOs.Person..ctor()
Class: 003b193c
MethodTable: 003b3458
mdToken: 06000008
Module: 003b2f2c
IsJitted: yes
CodeAddr: 00490200



          5.      Review the constructor's assembly code:
           Now we could review the method code using the Code Address above.
-        !U 00490200
-        Output:

!U 00490200

Normal JIT generated code
DotNetTypeInternals.BOs.Person..ctor()
Begin 00490200, size 36
>>> 00490200 55               push        ebp
00490201 8BEC             mov         ebp,esp
00490203 57               push        edi
00490204 56               push        esi
00490205 53               push        ebx
00490206 83EC30           sub         esp,30h
00490209 33C0             xor         eax,eax
0049020B 8945F0           mov         dword ptr [ebp-10h],eax
0049020E 33C0             xor         eax,eax
00490210 8945E4           mov         dword ptr [ebp-1Ch],eax
00490213 894DC4           mov         dword ptr [ebp-3Ch],ecx
00490216 833DE4303B0000   cmp         dword ptr ds:[003B30E4h],0
0049021D 7405             je          00490224
0049021F E8B5180A68       call        68531AD9 (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
00490224 8B4DC4           mov         ecx,dword ptr [ebp-3Ch]
00490227 E8E4375467       call        679D3A10 (System.Object..ctor(), mdToken: 06000001)
0049022C 90               nop
0049022D 90               nop
0049022E 8D65F4           lea         esp,[ebp-0Ch]
00490231 5B               pop         ebx
00490232 5E               pop         esi
00490233 5F               pop         edi
00490234 5D               pop         ebp
00490235 C3               ret

We could use this to find relevant issues, and also observe the method's prologue & epilogue.


          6.      Review the Method's IL Code (using the Method Descriptor address):
-        !DumpIL 003b344c
-        Output:

!DumpIL 003b344c

ilAddr = 012320c2
IL_0000: ldarg.0
IL_0001: call System.Object::.ctor
IL_0006: nop
IL_0007: ret




Conclusions & Remarks:

1.      We saw in the example above, using a simple class (Person) with only one property (int Id) that each instance comprises additional 2 fields (SBI, TOP), this means that Reference Types have additional overhead, which enlarge their memory consumption (as opposed to Value Types).
2.      The SBI - Sync Block Index (a.k.a Object Word Header) and the MT Pointer (a.k.a TOP - Type Object Pointer) are being used by the JIT Compiler and the CLR, and the developer can't access or used them.
3.      And we used the sos.dll debugger extension to explore all the information described above, and demonstrated some of its command.
4.      In other posts I'll elaborate on debugging using different extensions to find issues, such as: memory leaks, performance issues, exceptions and so on.
_________________________________________________________________________________________



  


Value Type internals

Value types that were declared in a method are allocated in the Stack memory of the executing Thread.
Value types that were declared as variables or properties in an object (Reference Type) are allocated in the Managed Heap (or LOH-Large Object Heap for large objects).

Allocation: (new variables)
The allocation operation is a very cheap operation, meaning it involves modifying the stack pointer register - ESP (in a 32bit system x86).

De-allocation: (old objects)
The de-allocation operation is also very cheap; meaning we are reversing the modification to the stack pointer register, and actually not using that memory anymore (we don't need the GC, which only operates in the Managed Heap).



Value Type - memory layout

As we know by now, after reading this article 'Reference Type internals' & 'Reference Type - memory layout' sections reference types have additional overhead (SBI & TOP), value types, on the other hand, don't have any additional overhead. 
The properties we'll declare in a value type (struct) would be the only ones that would reside in the memory.

However, value type could cause boxing, which is relatively a high performance operation.

In addition, when creating a structure, the CLR stores its properties in the Thread Stack in the order in which they were declared.
This would mean that uncareful declaration of some properties in a structure could cause a lot of unused and unreachable memory, which also impact the performance of the application.

·        For additional detailed information, please refer to the 'Ref. Type Memory Layout – Alignment & Packing' post, section: Value Types – Structures.




Boxing

What is boxing?
Why boxing is an expensive operation?

Boxing occurs when the compiler identifies that a value type needs to be treated as a reference type, for example: invoking a method on a struct, assigning a value type to an object (e.g. method returns value type, and was assigned to object) etc.

In which case, it copies the value type from the Thread Stack to the Managed Heap (allocate memory and copies the contents), adds to (wrap) the memory layout the MT Pointer (Method Table Pointer) and the SBI (Sync Block Index), creates a proper Method Table for that objects, and in fact converts it to an object on all its implications.

All of these operations are performed at runtime, and naturally expensive operations from performance perspective. In addition it also adds 'pressure' on the garbage collector, since it has to manage and reclaim all the temporary boxed objects.

The conversion and the wrapping operations are considered as a "Box" operation.

If we'll examine the IL instructions of a boxing operation, we'll see that the compiler adds the 'Box IL instruction' for the JIT Compiler, which in turn, calls a method that performs all the operations discussed above. (We'll see an example later on)

Remark: The new boxed object is different from the original value type, meaning changes on the boxed object won't effect on the original value type, and vice versa.


Avoiding boxing
As best practices, and from performance perspective, we should avoid boxing as much as possible. We could also use generics to create designated generic objects and methods when needed.
(I'm not going to elaborate further on Boxing, as it's not the main topic of this article, but it's important to understand that when working with Value Types that need to me threated as Reference Types we'll get boxing, which we should avoid!)



Example:
In this example I'll demonstrate the value type memory layout in the different Visual Studio Debug Windows.
-        We'll see that the value type reside in the Thread Stack.
-        We'll see that the value type doesn't contain the MT pointer & SBI.
-        And we'll see how the compiler performs boxing by calling the 'Box IL instruction'.



1.     Create a Line structure (struct):
In order to present the Value Type memory layout I wrote the following 'Line' struct:

public struct Line
{
        public int Length { get; set; }
}

For simplicity and in order to review the 'Line struct' memory layout clearly, I added only one property.



2.     Create 2 Line variables:
Next, I'll create 2 'Line' variables and assign a proper length value for each:

private static void ValueTypeMemoryLayoutExample()
{
    Line line1 = new Line();
    line1.Length = 9;

    Line line2 = new Line();
    line2.Length = 5;
}



3.     Debugging in Visual Studio:

3.1 Disassembly window, Memory window & Registers window

I've started to debug the code above, and presented the values in the Thread Stack memory.






By observing the image above, we could conclude the following:

1.      Disassembly window: When creating line1, the compiler uses the EBP (Base Pointer 32bit architecture x86) register and deducts 0x3C hex value to store the line1 data in the Thread Stack.
2.      Registers window: So I copied the EBP value: 0x0028f2dc.
3.      Memory1 window: Paste it in the Address textbox of the Memory 1 window, and added the following expression and pressed enter: 0x0028f2dc - 0x3c.
4.      Now, I could see the Thread Stack memory values in the Memory 1 window.



3.2  Inspecting line1 & line2 values
Now we'll continue debugging and observe these values in the Thread Stack memory.





We could clearly see that line1 variable reside next to line2 variable (with only 1 property - length), and that the struct Value Type doesn't contains the MT Pointer nor the SBI fields, as opposed to Reference Type.




4.     Boxing Example:
I'll use the Line strcut to demonstrate the boxing operation.

I added the following method:

private static void ValueTypeBoxingExample()
{
    Line line = new Line();
    line.Length = 4;

    Type lineType = line.GetType();
}


Compiler invokes the boxing operation
In order to view that the compiler invokes the boxing operation on the Line struct prior to invoking the GetType() method, we could examine the MSIL code in 2 ways:

           1.      By using disassemblers, such as: .Net Reflector, ILSpy, ILDASAM etc.
           2.      By performing debugging using the sos.dll extension.

Just for fun, and without over elaborating, I'll present both ways:


Decompiling our code using ILSpy:




We could see that, prior to calling the Object.GetType() method for Line, the compiler invokes the boxing operation on the Line struct using the box MSIL instruction.



Examining the code using sos.dll:

I performed the following SOS commands in the immediate window:

          1.     Load the sos debugging extension:
          Command:  .load sos


          2.     View the Managed Heap objects:

          Command: !DumpHeap –stat
          Output snippet:

!DumpHeap –stat

total 7691 objects

Statistics:

      MT    Count    TotalSize Class Name

...

67eec53c        1           12 System.Security.PolicyManager
67ed6fd0        1           12 System.Collections.Comparer
67ed66fc        1           12 System.Resources.FastResourceComparer
001a34e8        1           12 TestPerfExample.Line
67f023cc        1           16 System.Globalization.GlobalizationAssembly
67efd094        1           16 System.Reflection.Cache.InternalCache
67efa8ac        1           16 System.SByte[]

...

67ed44f8     1807        90876 System.Object[]
67f03798      107       180912 System.Byte[]
67f00d28      910       181432 System.String


Total 7691 objects



After debugging and passing the 'Type lineType = line.GetType();' line in the code, we could clearly see that the Line resides in the Managed (GC) Heap, even though it was declared as a struct, which is a Value Type!

           So in fact, after the boxing operation, the Line is an object – Reference Type, with all its implications.

           We could see its Method Table, EEClass, Method Descriptors, etc…


           3.     View the Line boxed object Method Table:

           Command: !DumpMT -md 001a34e8
           Output:

!DumpMT -md 001a34e8

EEClass: 001a1a80
Module: 001a2f2c
Name: TestPerfExample.Line
mdToken: 02000004  (…\TestPerfExample.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6

--------------------------------------
MethodDesc Table
   Entry MethodDesc      JIT Name
6830d1f4   67cd4a24   PreJIT System.ValueType.ToString()
67e50e30   67cd49f0   PreJIT System.ValueType.Equals(System.Object)
67e12008   67cd4a18     NONE System.ValueType.GetHashCode()
67ec7700   67cd49a8   PreJIT System.Object.Finalize()
001ac0f8   001a34c8      JIT TestPerfExample.Line.get_Length()
001ac100   001a34d8      JIT TestPerfExample.Line.set_Length(Int32)


        We could see the new Line boxed object Method Table information.


           4.     View the Line boxed object EEClass:

           Command: !DumpClass 001a1a80
           Output:

!DumpClass 001a1a80

Class Name: TestPerfExample.Line
mdToken: 02000004 (…\TestPerfExample.exe)
Parent Class: 67cbd470
Module: 001a2f2c
Method Table: 001a34e8
Vtable Slots: 4
Total Method Slots: 4
Class Attributes: 100109 
NumInstanceFields: 1
NumStaticFields: 0

      MT    Field   Offset                 Type VT     Attr    Value Name
67f02f74  4000003        4         System.Int32  1 instance           k__BackingField



           5.     View the method 'ValueTypeBoxingExample()' IL instructions:

           Command: !Name2EE TestPerfExample.exe TestPerfExample.Program.ValueTypeBoxingExample
           Output:

!Name2EE TestPerfExample.exe TestPerfExample.Program.ValueTypeBoxingExample

Module: 001a2f2c (TestPerfExample.exe)
Token: 0x06000015
MethodDesc: 001a335c
Name: TestPerfExample.Program.ValueTypeBoxingExample()
JITTED Code Address: 006c00f8



           Command: !DumpIL 001a335c
           Output:

!DumpIL 001a335c

ilAddr = 00d3231c
IL_0000: nop
IL_0001: ldloca.s VAR OR ARG 0
IL_0003: initobj TestPerfExample.Line
IL_0009: ldloca.s VAR OR ARG 0
IL_000b: ldc.i4.4
IL_000c: call TestPerfExample.Line::set_Length
IL_0011: nop
IL_0012: ldloc.0
IL_0013: box TestPerfExample.Line
IL_0018: call System.Object::GetType
IL_001d: stloc.1
IL_001e: ret


       We could see that from the IL code that the compiler added the 'Box IL instruction'.





Summary

We discussed Reference & Value Type internals and presented the memory layout of each, by observing the process memories (Managed Heap, Thread Stack) and also performed some debugging using the sos.dll extension, to verify our findings.




The End

Hope you enjoyed!
Appreciate your comments…

Yonatan Fedaeli