C# Objects in Memory - Expert C# 5.0: with .NET 4.5 Framework (2013)

Expert C# 5.0: with .NET 4.5 Framework (2013)

CHAPTER 2

images

C# Objects in Memory

This chapter examines the C# object and the relation between it and the stack and heap of the memory. The life of the value type stays in the stack, whereas the reference type stays in the heap. I will explain about these using the windbg.exe program and discuss the different sections of the memory while the CLR executes any .NET application.

Finally, this chapter will discuss boxing and unboxing by examining the stack and heap memory while executing a program.

Memory in .NET Applications

In .NET, when an application is run, the CLR uses two kinds of memory—stack and heap—to store value types and reference types. The CLR uses stack memory to store method-related information, which is called the Method state while executing a method, and it uses heap memory to store application-wide information. In the method state section, the CLR stores local variables, parameters, and method return addresses when it is finished executing it. In the heap memory, CLR stores all the objects (large and small objects) used by the application and Jitted code (the code compiled by the Just in Time [JIT]). The CLR allocates four sections of heap memory for storage while executing a managed application in .NET:

· Code heap: The code heap stores the actual native code instructions after they have been JIT compiled.

· Small object heap (SOH): The CLR stores allocated objects that are less than 85kB in size in the SOH.

· Large object heap (LOH): The LOH stores allocated objects greater than 85kB.

· Process heap: The process heap stores process-related information.

When the CLR starts executing a program, it allocates to the process heap, JIT code heap, garbage collector (GC) heap, and LOH, which is being structured into the system, shared, and default app domains. Figure 2-1 illustrates the memory CLR allocated while executing a .NET application, as given in Listing 2-1.

Listing 2-1. An Example of a C# Program

using System;

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
Person aPerson = new Person()
{
Name = "A"
};
}
}
public class Person
{
public string Name { get; set; }
}
}

When the CLR executes the program, as shown in Listing 2-1, it is required to maintain a stack for the methods of the Program class, heap to store the reference objects, and so on. So the CLR will allocate the memory for the program in Listing 2-1 at runtime, as shown in Figure 2-1.

images

Figure 2-1. Memory sections in .NET application

In .NET heap, the CLR manages a series of addresses to keep track of the execution of the program by storing the instances of the reference type needed by the application and the state of the application. In a C# class, when you define methods for executing those methods, the CLR stores local variables to process its task, parameters (if any) to get data from the caller, and return data, which is the output of the method to the caller. It also needs to keep information to return back to the execution point (by address). The .NET uses a data structure called Stack to keep track of all of this information.

images The memory information extracted via windbg.exe might be different when you run it locally.

The Stack

The Stack is the local or native storage table for a method, while the CLR executes that method. The lifetime of the Stack begins when the CLR instructs a particular method to execute. The CLR populates the Stack table with data passed as parameter sections of the Stack and stores the address of the object reference (where the calling method belongs) in this variable (provided by the CLR as part of the method call) in the Parameters section except for the static class. It stores local variables of the method in the Local section of the method stack. In addition to these, the CLR stores the return address when it finishes the execution of the method.

Here is an example that will help explain the concept of the stack. In Listing 2-2, Program class instantiates an instance of the TestClass and calls the TestMethod from the instance of the TestClass. So there will be two method calls that will take place while the CLR executes the following code, which we will call the Main method and the TestMethod derived from the Main method.

Listing 2-2. An Example of Stack Container Used in a Program

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
TestClass testClass = new TestClass();
testClass.TestMethod(10);
}
}

public class TestClass
{
public int TestMethod(int a)
{
return a + a;
}
}
}

The CLR has to keep track of the information for the Main method and TestMethod of the TestClass, so it will create a stack while it starts executing the Main method. When execution moves on and sees the

testClass.TestMethod(10);

line of code, the CLR will create another stack to keep related information for the TestMethod method on top of the stack of the Main method. The stacks of the Main and TestMethod will be stacked together, and the stack for the Main method will be at the bottom of that stack as it was called first and so on.

If you debug Listing 2-2 using the windbg.exe tool and execute the clrstack command, you can see the Stack information for the Main and TestMethod. Figure 2.2 explains the use of the Stack container of the Program class while in the execute mode.

images clrstack: It uses in the windbg.exe tool to determine the stack trace method in the managed application.

images

Figure 2-2. Stack container of the Program class while in execute mode

From Figure 2-2, you can easily see the stack state of the Main method when the CLR starts to execute the program. In the Stack of the Main method, the CLR maintains the arguments passed to the Main method in the Parameters section and address (0x0024ec8c), holding the testClass (0x01fdb64c) instance of the TestClass in the LOCALS section.

The lifetime of the TestMethod has not yet begun, as it hasn’t been called by the CLR to execute. The CLR has not yet created a stack for the TestMethod. As soon as the CLR starts executing TestMethod, it will create a stack and put that on the top of the Main method’s stack, as shown in Figure 2-2. In the stack of the TestMethod method, CLR stores the value of a and this parameters stored in the Parameters section and the results of the operation (a+a=20 (in hex 0x14)) into the LOCALS section of the method stack. While the CLR finishes the execution of the TestMethod, the stack of the TestMethod will end and the CLR passes the program pointer back to the Main method. If the application has multiple threads, then each thread will have its own stack.

The lifetime of the stack for a method ends when the method execution ends, or the CLR keeps the stack of the method alive if the method calls another method from itself. Until that method finishes, the CLR keeps the stack alive for the caller method. For example, if the CLR executes method A and A then calls method B, until B finishes the execution the CLR will keep alive the stack life for method A. A practical example of this is recursion. An example of the factorial calculation using the recursion algorithm is presented in Listing 2-3.

Listing 2-3. Example of the Factorial Recursion Algorithm

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
Math fc = new Math();
var result = fc.Factorial(3);
}
}

public class Math
{
public int Factorial(int a)
{
if (a <= 1)
return a;
else
return a * Factorial(a - 1);
}
}
}

Figure 2.3 demonstrates the execution model and the stack creation and lifetime.

images

Figure 2-3. Stack lifetime in method invocation

From Figure 2-3 you can see that the CLR allocates Stack for the Main method as well as for the Factorial method on each call of this method. These stacks will be placed on top of each other. The CLR keeps this Stack alive until it finishes with the respective methods. Figure 2-3 also shows that the CLR removes the Stack of the relevant method from the top of the stack container as soon as it finishes with the method.

The Heap

The heap in .NET is used to store all the reference types, such as:

· Classes

· Interfaces

· Delegates

· Strings

· Instances of objects

The CLR stores the instances of the reference types in either the LOH or SOH (depending on the size of the objects). When the CLR instantiates any reference type, it instantiates on the heap and it assigns an address to it that refers to the stack or the place from where this instance of the reference type is referenced. Listing 2-4 is an example of a reference type instantiation and the related heap while executing the program.

Listing 2-4. An Example of TestClass Object into the Heap

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
TestClass testClass = new TestClass();
}
}

public class TestClass
{
}
}

Although the CLR executes the above program in the Main method, it creates the instance (testClass) of the TestClass and stores that in the heap and assigns an address, for example, 0x0184b64c (address might be different on your machine while debug via windbg.exe) to it, as demonstrates in Figure 2-4.

images

Figure 2-4. The stack and heap combination showing how the CLR stores an object in the heap

The address (0x0184b64c) is used later to access the object from the application, for instance, from the Main method. Figure 2-4 demonstrates that while CLR is executing the statement

TestClass testClass= new TestClass();

it creates an instance of the TestClass on the heap and assigns an address (0x0184b64c) to that instance into the heap while putting the address into the stack of the Main method for access. To explore this further, you can debug the executable produced by the above code listing in the windbg.exe and run the clrstack command, which will give the following information (address might be different when you debug locally) about the stack and memory address stored in the local variables of the Main method stack:

0:000> !clrstack -a
OS Thread Id: 0x158c (0)
Child SP IP Call Site
0015f238 003d00a9 Ch02.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch02\Program.cs @ 8]
PARAMETERS:
args (0x0015f240) = 0x0184b63c
LOCALS:
0x0015f23c = 0x0184b64c /* Address of the TestClass */

0015f474 540121db [GCFrame: 0015f474]

From this code you can see the address 0x0015f23c in the LOCALS section of the stack is storing the address (0x0184b64c) of an instance of a reference type, in this case it is TestClass. To find out more about this object in windbg.exe, you can use the dumpobj command along with the address 0x0184b64c, which will give the following information (again the address might be different when you debug locally) as output about the TestClass:

0:000> !dumpobj 0x0184b64c
Name: Ch02.TestClass
MethodTable: 00343884
EEClass: 00341494
Size: 12(0xc) bytes
File: J:\Book\ExpertC#2012\SourceCode\BookExamples\Ch02\bin\Debug\Ch02.exe
Fields:
None

This demonstrates that the CLR instantiates a reference type on the heap and uses the address of that object from the stack to access it.

Value and Reference Types

In .NET, the CLR stores the value type in the stack unless you perform the boxing operation for the value type, under which circumstance the boxed type will be stored in the heap. The reference type will always store into the heap.

Let’s examine this in depth, as shown in Listing 2-5, where byte, int, float, long, bool, char, IntPtr, and string are used to declare value type variables. The ReferenceType class is used to declare a reference type variable to show where the CLR stores this type in runtime.

Listing 2-5. Example of the Value and Reference Types

using System;

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
byte aByte = 1;
int aInt = 10;
float aFloat = 10.5f;
long aLong = 10;
bool aBool = true;
char aChar = 'C';
IntPtr aIntPtr = IntPtr.Zero;
string aString = "string literal";

ReferenceType referenceType = new ReferenceType();

Console.WriteLine("Finish the execution");
}
}

public class ReferenceType { }
}

Based on Listing 2-5, the CLR allocates all the value types of the Main method into the stack, and the instance of the ReferenceType class (which instantiates on the heap) stores this in the stack of the Main method, which is used from the Main method when needed.

Let’s explore the stack and heap status while executing the above program using windbg.exe. In this test you can use the clrstack -a command in the windbg.exe tool, which will produce the following output (address might be different when you debug locally):

0:000> !clrstack -a
OS Thread Id: 0x1148 (0)
Child SP IP Call Site
001af084 002e0123 Ch02.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch02\Program.cs @ 20]
PARAMETERS:
args (0x001af0b0) = 0x01f9b63c
LOCALS:
0x001af0ac = 0x00000001 // 1
0x001af0a8 = 0x0000000a // 10
0x001af0a4 = 0x41280000 // 10.5f
0x001af09c = 0x0000000a // 10
0x001af098 = 0x00000001 // true
0x001af094 = 0x00000043 // ‘C’
0x001af090 = 0x00000000 // IntPtr.Zero
0x001af08c = 0x01f9b64c // instance of the String
0x001af088 = 0x01f9b6b0 // instance of the ReferenceType

001af2e4 52b721db [GCFrame: 001af2e4]

From this output, all of the value types are stored as literal values of the respective types in the stack of the Main method, such as 0x00000001 for the 1, 0x0000000a for the 10, 0x41280000 for the 10.5f, 0x0000000a for the 10, 0x00000001 for the true, 0x00000043 for the C, 0x00000000 for the IntPtr.Zero stored as a literal value of the relevant type into the stack. The reference type instance, for example, the string and ReferenceType object, is stored in the stack with the address 0x01f9b64c for the aString variable and 0x01f9b6b0 for the ReferenceType.

In the next step, we will find details about information the object stored in the 0x01f9b64c and 0x01f9b6b0 addresses on the heap. If you explore the 0x01f9b64c address from the heap, you can see that the heap maintains object information in the heap with the following information (address might be different when you debug locally):

0:000> !dumpobj 0x01f9b64c
Name: System.String
MethodTable: 520bf9ac
EEClass: 51df8bb0
Size: 42(0x2a) bytes
File: C:\Windows\Microsoft.NET\assembly\GAC_32\
mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: string literal
Fields:
MT Field Offset Type VT Attr Value Name
520c2978 40000ed 4 System.Int32 1 instance 14 m_stringLength
520c1dc8 40000ee 8 System.Char 1 instance 73 m_firstChar
520bf9ac 40000ef 8 System.String 0 shared static Empty
>> Domain:Value 00380c58:01f91228 <<

0:000> !dumpobj 0x01f9b6b0
Name: Ch02.ReferenceType
MethodTable: 00233884
EEClass: 002314a4
Size: 12(0xc) bytes
File: J:\Book\ExpertC#2012\SourceCode\BookExamples\Ch02\bin\Debug\Ch02.exe
Fields:
None

So the address 0x01f9b64c points to the instance of the string object and 0x01f9b6b0 to the instance of the ReferenceType from the heap.

Instantiating an Object

When you instantiate a type in .NET, it becomes an object that is the memory representation of that type. You can use the new keyword in .NET to instantiate an instance of the reference type. From the following program, you can find out how a class, interface, or struct type is instantiated by the CLR, as shown in Listing 2-6.

Listing 2-6. Example of Instantiation

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
int aInt = 2012;
TestClass aTestClass = new TestClass();
AInterface aInterfaceImplementation = new InterfaceImplementation();
AStruct aAStruct = new AStruct();
}
}

public class TestClass
{
public void TestMethod(){/*Code removed*/}
}

public interface AInterface
{
void AMethod();
}

public class InterfaceImplementation : AInterface
{
public void AMethod() {/*Code removed*/}
}

public struct AStruct
{
public int ANumber { get; set; }
}
}

We can decompile this code into IL code using ildasm.exe, as demonstrated in Listing 2-7.

Listing 2-7. IL Code of Program

.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
/* Code removed */
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] int32 aInt,
[1] class Ch02.TestClass aTestClass,
[2] class Ch02.AInterface aInterfaceImplementation,
[3] valuetype Ch02.AStruct aAStruct)

L_0000: nop
L_0001: ldc.i4 0x7dc
L_0006: stloc.0

/* CLR creates an instance of TestClass on the heap and
* store the address of the instance into local stack location
* 1 of this Main method */

L_0007: newobj instance void Ch02.TestClass::.ctor()
L_000c: stloc.1

/* CLR creates an instance of InterfaceImplementation on the heap and
* store the address of the instance into local stack location
* 2 of this Main method */

L_000d: newobj instance void Ch02.InterfaceImplementation::.ctor()
L_0012: stloc.2

/* CLR will load the local address of the aAStruct using ldloca
* instruction and initialize the default value for that address
* using initobj instruction */

L_0013: ldloca.s aAStruct
L_0015: initobj Ch02.AStruct
L_001b: ret
}
}

From the code in Listing 2-7, L_0007, CLR uses newobj IL instruction:

· It allocates a new instance of the class associated with .ctor and initializes all the fields in the new instance to 0 for the value type or null for the reference type.

· The CLR calls the constructor with the given arguments along with the newly created instance and the initialized object reference is pushed on the stack.

images.ctor: This refers to the constructor in IL. In the Explore .ctor and .cctor using ildasm.exe section of Chapter 15, there is a discussion about .ctor and .cctor.

So while the above code executes through the CLR, it instantiates the reference type on the heap and assigns an address to it. The CLR then assigns that address back to the stack for later access. Let’s explore the heap for the TestClass and InterfaceImplementation, as shown in this code:

0:000> !dumpheap -type TestClass
Address MT Size
01d2b91c 00133938 12
total 0 objects
Statistics:
MT Count TotalSize Class Name
00133938 1 12 Ch02.TestClass
Total 1 objects

0:000> !dumpheap -type InterfaceImplementation
Address MT Size
01d2b928 00133a10 12
total 0 objects
Statistics:
MT Count TotalSize Class Name
00133a10 1 12 Ch02.InterfaceImplementation
Total 1 objects

The TestClass and InterfaceImplementation are on the heap, and the CLR assigns address 01d2b91c for the instance of the TestClass and 01d2b928 for the InterfaceImplementation class. Figure 2-5 shows that the CLR stores the instances of the TestClass and InterfaceImplementation at the 01d2b91c and 01d2b928 addresses on the heap and stores these addresses to the stack of the Main method.

images

Figure 2-5. Instance of the reference type and the heap and stack

When we examine the stack of the Main method, you can see that 0x01d2b91c and 0x01d2b928 have been stored as local variables.

0:000> !clrstack -a
OS Thread Id: 0x7b8 (0)
Child SP IP Call Site
002af098 003800e1 Ch02.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch02\Program.cs @ 13]
PARAMETERS:
args (0x002af0b0) = 0x01d2b90c
LOCALS:
0x002af0ac = 0x000007dc /* literal of int 2012 */
0x002af0a4 = 0x01d2b91c /* Address of the TestClass */
0x002af0a0 = 0x01d2b928 /* Address of the InterfaceImplementation */

0x002af0a8 = 0x00000000

002af2e4 673621db [GCFrame: 002af2e4]

And the Object information,

0:000> !dumpobj 0x01d2b91c
Name: Ch02.TestClass
MethodTable: 00133938
EEClass: 0013159c
Size: 12(0xc) bytes
File: J:\Book\ExpertC#2012\SourceCode\BookExamples\Ch02\bin\Debug\Ch02.exe
Fields:
None

0:000> !dumpobj 0x01d2b928
Name: Ch02.InterfaceImplementation
MethodTable: 00133a10
EEClass: 00131608
Size: 12(0xc) bytes
File: J:\Book\ExpertC#2012\SourceCode\BookExamples\Ch02\bin\Debug\Ch02.exe
Fields:
None

And from this experiment you can see that struct does not instantiate on the heap (unless you box the struct), so there is no reference of it. You can see from the IL that it is created using the initObj instruction, which initializes the address of the struct local variable with a default value.

Boxing and Unboxing

Boxing is the process where the CLR uses the value type, such as int, float, long, and so forth, to wrap into an instance of the system.object type or more specifically into the related type. For example, the value of the int into the System.Int32 and the reverse will give a value, and this process is calledunboxing. Listing 2-8 presents an example where the variable aInt of the type int has been declared.

Listing 2-8. An Example of Boxing and Unboxing

using System;

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
int aInt = 2012;
string aStringLiteral = "Expert C# 5.0: with the .NET 4.5 Framework";
TestClass testClass = new TestClass();

Console.WriteLine("{0} {1}.",
testClass. CastingString(aStringLiteral),
testClass.BoxInt(aInt));
}
}

public class TestClass
{

public int BoxInt(object aInt)
{
int unboxedInt = 0;
unboxedInt = (int)aInt;
return unboxedInt;
}

public string CastingString(object aStringLieteral)
{
string unboxedString = string.Empty;
unboxedString = (string)aStringLieteral;
return unboxedString;
}
}
}

This program will produce the following output.

Expert C# 5.0: with the .NET 4.5 Framework.

In the TestClass the BoxInt method will accept an object type input. While executing the

testClass.BoxInt(aInt);

statement, the CLR will convert the aInt variable into an instance of the System.Int32 type, which will hold the value of the 2012, and passes this object to the BoxInt method. Listing 2-9 presents the IL code produced for Listing 2-8, extracted via ildasm.exe.

Listing 2-9. IL Code of the Program Class

.class private auto ansi beforefieldinit Program
extends [mscorlib]System.Object
{
/* Code removed */

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 4
.locals init (
[0] int32 aInt,
[1] string aStringLiteral,
[2] class Ch02.TestClass testClass)

/* Code removed */
L_001f: ldloc.2
/* load the value from the local variable location
* 0 into the evaluation stack*/
L_0020: ldloc.0

/* CLR will instantiate the type of the System.Int32
* and store the top value from the evaluation stack
* due to the box instruction.*/
L_0021: box int32

L_0026: callvirt instance int32 Ch02.TestClass::BoxInt(object)
L_002b: box int32
L_0030: call void [mscorlib]System.Console::WriteLine(string, object, object)
L_0035: nop
L_0036: ret
}
}

From the IL code in Listing 2-9, you can see that in the runtime the CLR will load the value from the local variable section of the Main method stack into the evaluation stack in the L_0020 label. In the execution of the IL instruction in L_0021, the CLR will instantiate an instance of the System.Int32with a top value from the evaluation stack. Figure 2-6 illustrates the boxing process.

images

Figure 2-6. Stack and heap status while doing a boxing operation

From Figure 2-6, you can see that in the stack of the BoxInt method the aInt variable is holding an address (0x01f9b968) of the object from the heap that is being created by the IL instruction box and holding the value 2012. If you explore the address 0x01f9b968 in windbg.exe, you will find the memory information about the object, for example:

0:000> !dumpobj 0x01f9b968
Name: System.Int32
MethodTable: 79332978
EEClass: 79069cf4
Size: 12(0xc) bytes
File: C:\Windows\Microsoft.NET\assembly\GAC_32\
mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
79332978 400046b 4 System.Int32 1 instance 2012 m_value

The stack information for the Main method will show that the aInt variable in the LOCALS section holds the address of 0x01f9b968.

0:000> !clrstack -a
OS Thread Id: 0x1618 (0)
Child SP IP Call Site
0016eea4 003e0244 Ch02.TestClass.BoxInt(System.Object) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch02\Program.cs @ 22]
PARAMETERS:
this (0x0016eeb0) = 0x01f9b95c
aInt (0x0016eea4) = 0x01f9b968 /* a object stored onto the Heap*/
LOCALS:
0x0016eeac = 0x00000000
0x0016eea8 = 0x00000000

0016eebc 003e010d Ch02.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch02\Program.cs @ 12]
PARAMETERS:
args (0x0016eef0) = 0x01f9b90c
LOCALS:
0x0016eeec = 0x000007dc /* 2012 */
0x0016eee4 = 0x01f9b91c
0x0016eee0 = 0x01f9b95c

0016f120 673621db [GCFrame: 0016f120]

While unboxing, the CLR will get the value from the boxed object and initialize a literal value for the same boxed type. For example, as in Listing 2-8, the unboxing type for the aInt will be int32. The decompiled IL code of the TestClass is shown in Listing 2-10.

Listing 2-10. IL Code for the TestClass

.class public auto ansi beforefieldinit TestClass
extends [mscorlib]System.Object
{
/* Code removed*/
.method public hidebysig instance int32 BoxInt(object aInt) cil managed
{
.maxstack 1
.locals init (
[0] int32 unboxedInt,
[1] int32 CS$1$0000)

/* CLR will load the argument 1 which is the boxed value
* of the aInt variable passed from the Main method.*/
L_0003: ldarg.1

/* CLR will convert the value of aInt object into the value
* of the int32 and store into the local variable location 0
* for the unboxedInt variable */
L_0004: unbox.any int32
L_0009: stloc.0

L_000a: ldloc.0
L_000b: stloc.1
L_000c: br.s L_000e
L_000e: ldloc.1
L_000f: ret
}

.method public hidebysig instance string CastingString(
object aStringLieteral) cil managed
{
.maxstack 1
.locals init (
[0] string unboxedString,
[1] string CS$1$0000)
L_0000: nop
L_0001: ldsfld string [mscorlib]System.String::Empty
L_0006: stloc.0
L_0007: ldarg.1
L_0008: castclass string
L_000d: stloc.0
L_000e: ldloc.0
L_000f: stloc.1
L_0010: br.s L_0012
L_0012: ldloc.1
L_0013: ret
}
}

So in the process of unboxing, CLR will unbox the value from the object referred by 0x01f9b968 into its value and store it in the local variable 0x0016eeac, as demonstrated in Figure 2-7.

images

Figure 2-7. Stack and heap status while doing an unboxing operation

Let’s explore this in the runtime using windbg.exe program to see the stack information while the CLR is executing the BoxInt method of the TestClass shown in Listing 2-8. In the windbg.exe command prompt, if you run clrstack -a you will get the following memory information (address might be different when you debug locally):

0:000> !clrstack -a
OS Thread Id: 0x1618 (0)
Child SP IP Call Site
0016eea4 003e0271 Ch02.TestClass.BoxInt(System.Object) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch02\Program.cs @ 25]
PARAMETERS:
this (0x0016eeb0) = 0x01f9b95c
aInt (0x0016eea4) = 0x01f9b968 /* a object stored onto the Heap */
LOCALS:
0x0016eeac = 0x000007dc /* 2012 */
0x0016eea8 = 0x000007dc

0016eebc 003e010d Ch02.Program.Main(System.String[]) [J:\Book\ExpertC#2012\SourceCode\
BookExamples\Ch02\Program.cs @ 12]
PARAMETERS:
args (0x0016eef0) = 0x01f9b90c
LOCALS:
0x0016eeec = 0x000007dc
0x0016eee4 = 0x01f9b91c
0x0016eee0 = 0x01f9b95c

0016f120 673621db [GCFrame: 0016f120]

In the BoxInt method, CLR stores the unboxed value (0x000007dc equivalent decimal is 2012) to get from the boxed type instance located on the heap at 0x01f9b968 into the 0x0016eeac address of the LOCALS section of the BoxInt method stack. In day-to-day programming, you use boxing and unboxing without noticing, for example, in the following code using boxing and unboxing underneath:

ArrayList aListOfNumbers = new ArrayList();
aListOfNumbers.Add(1);
aListOfNumbers.Add(2);
int result = (int)aListOfNumbers[1];

The decompiled IL code for this code demonstrates how the CLR uses the boxing and unboxing operation for the item to go in and out of the ArrayList:

.method private hidebysig static void Main(string[] args) cil managed
{
/* Code removed */

/* The CLR does the boxing in the insertion into the
* ArrayList while executing the Add method of the ArrayList.
*/
L_0009: box int32
L_000e: callvirt instance int32
[mscorlib]System.Collections.ArrayList::Add(object)
/* Code removed */

/* The CLR does the boxing in the insertion into the
* ArrayList while executing the Add method of the ArrayList.
*/
L_0016: box int32
L_001b: callvirt instance int32
[mscorlib]System.Collections.ArrayList::Add(object)
/* Code removed */

L_0023: callvirt instance object
[mscorlib]System.Collections.ArrayList::get_Item(int32)
/* The CLR unbox the return value from the get_Item while executing
* (int)aListOfNumbers[1] line of code.
*/
L_0028: unbox.any int32
L_002d: stloc.1
L_002e: ret
}

Performance in Boxing and Unboxing Operation

Boxing and unboxing are time-consuming processes that might affect the performance of an application. The boxing operation can take up to 20 times more time than the assignment operation. The right use of the boxing operation is important when the performance is the key factor for your application. Let’s find out how boxing and unboxing operations affect the performance of an application. In Listing 2-11, a list of int is stored in the instance of the List<int> type. And Listing 2-12 uses ArrayList for storage.

Listing 2-11. List Creation

using System;
using System.Collections.Generic;
namespace Ch02
{
class Program
{
static void Main(string[] args)
{
IList<int> ll = new List<int>();
for (int i = 0; i <= Int16.MaxValue * 2; ++i)
ll.Add(i);
foreach (int st in ll) ;
}
}
}

Listing 2-12. ArrayList Creation

using System;
using System.Collections;

namespace Ch02
{
class Program
{
static void Main(string[] args)
{
ArrayList ll = new ArrayList();
for (int i = 0; i <= Int16.MaxValue * 2; ++i)
ll.Add(i);
foreach (int st in ll) ;
}
}
}

Figure 2-8 profiles Listings 2-11 and 2-12 using the ClrProfiler.exe tool to find out how much memory the ArrayList consumes while adding items into it compared to using List<int>.

images

Figure 2-8. Performance measurement of the boxing and unboxing operation

Figure 2-8 demonstrates that the ArrayList consumes about 1,365,145 bytes, whereas the List class consumes only 58,674 bytes for the same number of items. As you saw earlier, the ArrayList uses the boxing and unboxing operation to move items in and out of it, which requires a lot of memory. The List class does not use the boxing and unboxing operation, which makes it more effective in performance compared with using ArrayList.

Garbage Collection

When you create an instance of a type in .NET, for example, a reference type, using the new keyword, the CLR takes care of the rest. For example, it will instantiate it onto the heap, allocate extra memory if required, and deallocate the memory when you finish with that object. The CLR takes care of this memory reclaim process using the GC. The GC maintains information about object usage and uses this information to make memory management decisions, such as where in the memory to locate a newly created object, when to relocate an object, and when an object is no longer in use or inaccessible.

In .NET, automatic memory cleanup is achieved using the GC algorithm. The GC algorithm looks for the allocated objects on the heap and tries to determine if that object is being referenced by anything; if it is not, it will allocate it for collection or to the cleanup cycle. There are several possible sources of these references:

· Global or static object references

· Central processing unit (CPU) registers

· Object finalization references (more later)

· Interop references (.NET objects passed to Component Object Model (COM)/Application programming interface (API) calls)

· Stack references

To clean up the objects, GC needs to traverse a number of objects to determine whether they can be collected for cleanup. The CLR uses the concept of longibility of the object in the memory. For example, when the object is in use for a long time, it is less likely to lose the reference, whereas a newly created object is more likely to be cleaned up.

In GC, three generations of object groups are used:

· Generation 0

· Generation 1

· Generation 2

Generation 0

Generation 0 (Gen 0) is the youngest group and it contains short-lived objects. An example of a short-lived object is a temporary variable. GC occurs most frequently in this generation. Newly allocated objects form a new generation of objects and are implicitly Gen 0 collections, unless they are large objects, in which case they go on the LOH in a Gen 2 collection. Most objects are reclaimed for GC in Gen 0 and do not survive to the next generation.

Generation 1

Gen 1 contains short-lived objects and serves as a buffer between short-lived objects and long-lived objects.

Generation 2

Gen 2 contains long-lived objects. An example of a long-lived object is a server application that contains static data that are live for the duration of the process.

The life of an object starts in Gen 0. If the objects in Gen 0 survive, GC promotes them to Gen 1, and likewise for Gen 1 objects to Gen 2. The objects in Gen 2 stay in Gen 2. Gen 0 objects are collected frequently, so short-lived objects are quickly removed. Gen 1 objects are collected less frequently, and Gen 2 objects even less frequently. So the longer an object lives, the longer it takes to remove from memory once it has lost all references. When Gen 1 objects are collected, the GC gathers Gen 0 objects as well. In addition, when Gen 2 objects are collected, those in Gen 1 and Gen 0 are also collected. As a result, higher generation collections are more expensive.

A GC has the following phases to clean up the objects:

· A marking phase that finds and creates a list of all live objects.

· A relocating phase that updates the references to the objects that will be compacted.

· A compacting phase that reclaims the space occupied by the dead objects and compacts the surviving objects. The compacting phase moves objects that have survived GC toward the older end of the segment.

The Gen 2 collections can occupy multiple segments; objects that are promoted into Gen 2 can be moved into an older segment. Both Gen 1 and Gen 2 survivors can be moved to a different segment, because they are promoted to Gen 2.

The LOH is not compacted, because this would increase memory usage over an unacceptable length of time.

Summary

In this chapter we have learned about the usage of the memory by the CLR when it executes an managed application. We have examined the stack and the heap, how they are used by the CLR to store data for the value type and reference type in the application, and the steps in boxing and unboxing operations. We also saw how the boxing and unboxing operation affects the performance of the application. In the next chapter, we will learn how the parameter in .NET sets the value and reference type parameter, what happens when you pass the parameter using the out and ref keywords, and how CLR takes care of the named parameter.