1. System.Object
The runtime requires each type to eventually be System.Object
derived 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 this exactly 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 new
be created using operators. for example:
Apple apple = new Apple();
During this period, new
the 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.Object
and returned.
new
After 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 Type
represented by an instance of a type. A type object pointer is just Type
a pointer to that instance. Of course, Type
the 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 M1
method:
void M1()
{
string name = "Joe";
M2(name);
// ...
return;
}
void M2(string s)
{
Int32 length = s.Length;
Int32 tally;
// ...
return;
}
name
First execute the first sentence of code, you need to allocate memory for local variables on the thread stack
Then M1
call M2
the method, you need to name
pass the local variable as an actual parameter. Therefore name
the address in the local variable needs to be pushed onto the stack ( M2
internally used to s
identify the position in the stack)
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
Next, start executing M2
the code. First allocate memory for local variables length
andtally
Finally, when the statement M2
is reached return
, the instruction pointer of the CPU is set to the return address in the stack, M2
and the stack frame is expanded and restored to the following figure
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:
EBP
ESP
Initially, M1
the caller function 栈基址
( EBP
the address pointed to) is pushed onto the stack and saved, and both EBP
and ESP
are pointed to this location
Next, push the local variables name
and parameters s
onto the stack, and ESP
move accordingly
Next, you need to call. M2
First, you need to push M2
the return address of the stack (used to indicate M2
which instruction to execute next after the execution is completed)
Then you need to push the current EBP
address (that is, M1
the stack base address) to the stack to M2
restore it after the call is completedEBP
will EBP
be moved to the ESP
same position as and, at this time it is officially entered M2
the stack frame
Next, push the parameters s
to the stack, local variables length
and tally
push the stack
Then M2
returned, at this time will ESP
move to EBP
position
Because the stack base address is EBP
stored in the memory space referred to at this time, you can directly jump to this locationM1
EBP
Then continue to pop the stack, ESP
move back to M2的返回地址
the position, and continue to execute M1
subsequent instructions
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
)
When the JIT compiler converts M3
the IL code into native instructions, it will collect all the types referenced inside the method (such as Employee
, Manager
. String
, Int32
etc. 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.
When the CLR confirms that all types of objects required by the method have been created and M3
the code has been compiled, it allows the thread to execute M3
the native code.
First allocate thread stack space for local variables and set default values (null or 0)
Next the code constructs an Manager
object, which creates Manager
an instance of a type on the managed heap. Manager
It 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
.
Employee
A 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 JIT
compiler 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 Manager
object and Joe
initializes it with this parameter. The method finally returns the address of the object. This address will be stored in a variable e
.
At this time, an unreferenced Manager
object 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 Employee
the non-virtual instance method GetYearsEmployed()
. For non-virtual instance methods, JIT
the compiler will find the caller's type object (here, Employee
the 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.
The next code will call Employee
the virtual instance method GetProgressReport()
. When calling a virtual instance method, JIT
first check the variable that made the call, and then go to the object that made the call (here, the representative object) according to the Joe
address 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.
Manager
Here 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 .Joe
Employee
Manager
Employee
Employee
GetProgressReport()
Finally, use the diagram to illustrate the "type object pointer" mentioned in the first section
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