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:
{
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
|
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