CLR via C# (2) thread stack and managed heap

1. System.Object

The runtime requires each type to eventually be System.Objectderived from, and it provides the following basic methods:

method name illustrate
Equals() virtual method. If two objects have the same value, returntrue
GetHashCode() virtual method. Returns the hash code of the object
ToString() virtual method. The full name of the default return type
GetType() non-virtual method. indicate what type the object is
MemberwiseClone() non-virtual method. Creates a new instance of the type whose instance fields are thisexactly the same as the instance fields of
Finalize() virtual method. Will be called before the object memory is reclaimed

The CLR requires that all objects newbe created using operators. for example:
Apple apple = new Apple();

During this period, newthe operator actually does the following things:

  • Computes the number of bytes required by all instance fields defined in the type and all of its base types . In addition, the number of bytes occupied by some additional members needs to be added, including: type object pointer , sync block index , and an undocumented internal field (used to calculate the hash value of the object instance).
  • Allocate the calculated number of bytes of memory from the managed heap, and initialize all binary bits to 0.
  • Initializes the "type object pointer" and "sync block index" of the object.
  • Invokes the type's constructor, passing the specified arguments. Each type's constructor is responsible for initializing its own instance fields. The constructor that will eventually be called System.Objectand returned.

newAfter performing these operations, a reference to the newly created object is returned.

There are a few nouns above that need to be explained:
Instance field : refers to a non-static field that belongs to an object. The opposite static field belongs to the class.
Type Object Pointer : Every object is an instance of a type, and every type is Typerepresented by an instance of a type. A type object pointer is just Typea pointer to that instance. Of course, Typethe type object itself is also an instance of the type object, and its type object pointer points to itself.
Synchronization block index : It can be simply understood as a pointer to a "synchronization block". The object that owns this synchronization block can support thread synchronization.

2. Thread stack

The stack allocated when the thread is created 1MB. The stack space is used to pass actual parameters to the method, and the local variables defined inside the method also exist on the stack. The stack is built from high memory addresses to low memory addresses .

Suppose a thread wants to call the following M1method:

void M1()
{
    
    
	string name = "Joe";
	M2(name);
	// ...
	return;
}
void M2(string s)
{
    
    
	Int32 length = s.Length;
	Int32 tally;
	// ...
	return;
}

nameFirst execute the first sentence of code, you need to allocate memory for local variables on the thread stack
image.png

Then M1call M2the method, you need to namepass the local variable as an actual parameter. Therefore namethe address in the local variable needs to be pushed onto the stack ( M2internally used to sidentify the position in the stack)
image.png

In addition, the calling method will also push the "return address" onto the stack, which is used to return to the original location after the method call ends
image.png

Next, start executing M2the code. First allocate memory for local variables lengthandtally
image.png

Finally, when the statement M2is reached return, the instruction pointer of the CPU is set to the return address in the stack, M2and the stack frame is expanded and restored to the following figure
image.png

Extension: stack frame

(PS: The following content is summarized after I checked different materials, and it is not necessarily correct. Please correct me if there is any error!) The thread stack operation
model above has been simplified. To understand the operation process of the thread stack in depth, you need some compilation foundation. The so-called "stack frame" is actually an area on a thread stack surrounded by two addresses stored in the two registers EBP (frame pointer) and ESP (stack pointer)
in the CPU . This area stores the parameters, return address, local variables, etc. of the function currently being executed. It is used to save the start address of the running function stack frame, and is used to save the end address of the running function stack frame. The change process of the stack frame during the execution of the above function is as follows:
EBPESP

Initially, M1the caller function 栈基址( EBPthe address pointed to) is pushed onto the stack and saved, and both EBPand ESPare pointed to this location
image.png

Next, push the local variables nameand parameters sonto the stack, and ESPmove accordingly
image.png

Next, you need to call. M2First, you need to push M2the return address of the stack (used to indicate M2which instruction to execute next after the execution is completed)
image.png

Then you need to push the current EBPaddress (that is, M1the stack base address) to the stack to M2restore it after the call is completedEBP
image.png

will EBPbe moved to the ESPsame position as and, at this time it is officially entered M2the stack frame
image.png

Next, push the parameters sto the stack, local variables lengthand tallypush the stack
image.png

Then M2returned, at this time will ESPmove to EBPposition
image.png

Because the stack base address is EBPstored in the memory space referred to at this time, you can directly jump to this locationM1EBP
image.png

Then continue to pop the stack, ESPmove back to M2的返回地址the position, and continue to execute M1subsequent instructions
image.png

3. Managed heap

During the running of the program, the CLR also maintains a heap for managing reference types , ie 托管堆. When the process is initialized, an address space area is designated by the CLR as a managed heap. When the area is filled with non-garbage objects, the CLR will allocate more areas until the entire process address space (limited by the virtual address space of the process, 32-bit processes can allocate up to 1.5GB, and 64-bit can allocate up to 8TB) is filled.

Next, take the following code as an example to explain the relationship between the managed heap and the thread stack, as well as types and objects at runtime

class Manager:Employee
{
    
    
	public override string GetProgressReport(){
    
    ...}
}

class Employee
{
    
    
	public Int32 GetYearsEmployed(){
    
    ...}

	public virtual string GetProgressReport(){
    
    ...}

	public static Employee LookUp(string name){
    
    ...}
}

void M3()
{
    
    
	Employee e;
	Int32 year;
	e = new Manager();
	e = Employee.LookUp("Joe");
	year = e.GetYearsEmployed();
	e.GetProgressReport();
}

First, the initial state of the thread stack and the managed heap is as follows (the thread has executed some code and will execute it soon M3)
image.png

When the JIT compiler converts M3the IL code into native instructions, it will collect all the types referenced inside the method (such as Employee, Manager. String, Int32etc. are not shown here), and confirm that the assemblies that define these types have been loaded. Then, using the assembly's metadata, some data structures are created on the managed heap to represent the types themselves.
image.png

When the CLR confirms that all types of objects required by the method have been created and M3the code has been compiled, it allows the thread to execute M3the native code.

First allocate thread stack space for local variables and set default values ​​(null or 0)
image.png

Next the code constructs an Managerobject, which creates Manageran instance of a type on the managed heap. ManagerIt contains all instance data fields defined by its base type , in addition to the type object pointer and synchronized block index . When a new object is created on the managed heap, the CLR automatically initializes the internal 类型对象指针pointer to the corresponding type object. Also initializes 同步块索引and sets all instance fields to null or 0. Next, the type constructor is called, and the new operator returns the memory address of the object. This address will be stored in a variable e.
image.png

EmployeeA static method that is called by the next line of code LookUp(). When calling a static method, the CLR will locate the type object that defines the static method, and then the JITcompiler will look up the record of the corresponding method in the type object side publication, and then compile the method in real time (if it has not been compiled before), and then call the compiled code.

We assume that this static method internally constructs a new Managerobject and Joeinitializes it with this parameter. The method finally returns the address of the object. This address will be stored in a variable e.
image.png

At this time, an unreferenced Managerobject will be generated in the managed heap. However, the subsequent garbage collection mechanism will automatically release the memory occupied by the object.

The next line of code calls Employeethe non-virtual instance method GetYearsEmployed(). For non-virtual instance methods, JITthe compiler will find the caller's type object (here, Employeethe type object). If the method is not found in the caller's type object, it will backtrack to the parent type object and backtrack all the way Object. Just-in-time compilation occurs when found. Let's say the method returned 5.
image.png

The next code will call Employeethe virtual instance method GetProgressReport(). When calling a virtual instance method, JITfirst check the variable that made the call, and then go to the object that made the call (here, the representative object) according to the Joeaddress Manager. Then according to the "type object pointer" of the object, its corresponding type object is found. Then look up the called method entry, and just-in-time compile it.
image.png

ManagerHere the method of the type object is called GetProgressReport(). But if it is not in LookUp()the method , then an object will be constructed internally , and the method of the type object will be called here .JoeEmployeeManagerEmployeeEmployeeGetProgressReport()
image.png

Finally, use the diagram to illustrate the "type object pointer" mentioned in the first section
image.png

4. References

[1]. Function stack frame function call principle
[2]. In the eyes of the CPU: {function brackets} | stack frame | stack | stack variable
[3]. "VLR via C# Fourth Edition"
[4]. C# managed heap and garbage collection

Guess you like

Origin blog.csdn.net/LWR_Shadow/article/details/131842398