. NET object creation, garbage collection, manual processing of unmanaged resources,. net garbage collection
This document sorts out object creation, garbage collection, and manual processing of unmanaged resources.
→ First run the application and create a Windows process.
→ CLR creates a continuous virtual address space, which is the hosting heap. In addition, this address space does not initially have a corresponding physical storage space.
The virtual address space is divided into two segments. A section is a normal heap, also called a GC heap. Instances of reference type objects smaller than 85000 bytes are allocated here. The other is a large object heap, instances of reference type objects larger than or equal to 85000 bytes are allocated here.
For client applications, the size of each segment is roughly 16 MB; for server applications, the size of each segment is roughly 64 MB. In addition, the size of each segment varies depending on the number of CPUs, whether it is a 32-bit or 64-bit operating system.
As each segment is filled with objects, CLR allocates more segments until the entire process space is full. Each process can use 4 GB of memory.
The managed stack maintains a pointer NextObjPtr pointing to the location of the next object allocated on the managed stack.
The managed Heap is divided into GC Heap and Loader Heap based on different storage information. Garbage collection Heap GC Heap Storage object instance, managed by GC; load metadata information in AppDomain stored by Heap Loader Heap, such as base type, static field, static method, interface information, etc, it is not managed by GC. its lifecycle starts from AppDomain creation to AppDomain uninstallation. In this case, the managed heap and other aspects are roughly distributed as follows:
→ Create an object on the managed Stack
□Create a reference type object
Use the new Keyword to create a reference type object.
FileStream fs = new FileStream (@ "", FileMode. Open );
The above code is compiled by the compiler and is actually a newobj command in the generated intermediate IL code. Commands related to IL also include ldstr commands used to create string objects, newarr is used to allocate new array objects, and box commands are used to convert values to reference types, copy the value type field to the managed stack.
Run the following code to understand the process of creating an object on the managed Stack:
public class Employee
{
private int _id;
private Status _status;
public Employee()
{
_id = 1;
_status = new Status();
}
}
public class Sales : Employee
{
public bool _isLive;
public bool IsLive()
{
return _isLive;
}
public static void Main()
{
Sales _sales;
_sales = new Sales();
_sales._isLive = true;
Console.WriteLine(_sales.IsLive());
}
}
public class Status
{
private int _years;
private char _level = "A";
}
1. Execute Sales _ sales to open up 4 bytes of memory space on the thread stack, which is used to save the managed heap address of the Sales object. At this time, it is null.
2. _ sales = new Sales (): recursively calculates the total number of bytes of the Sales instance object, recursively starting from the Sales itself, to the parent class Employee, to the base class System. object. The specific byte calculation process is as follows:
Number of bytes of the Sales object instance = number of bytes of field _ isLive is 1 byte
Number of bytes attached to the Sales object instance: TypeHandler = 4 bytes
Number of bytes attached to the Sales object instance SyncBlockIndex = 4 bytes
Number of bytes of the parent class of the Sales object instance = number of bytes of field _ id 4 byte + field _ status save reference to the Status object instance as 4 byte
1 + 4 + 4 + 4 + 4 = 17 bytes. Considering that memory blocks are always aligned by 4 bytes, the memory is supplemented to 20 bytes.
Because a Status class is instantiated in the constructor of the parent class Employee of Sales, you also need to calculate the number of bytes of the Status object instance:
Status object instance byte Count = field _ years byte Count 4 byte + field _ level byte Count 2 byte
Number of bytes attached to the Status object instance: TypeHandler = 4 bytes
Number of bytes attached to the Status object instance SyncBlockIndex = 4 bytes
4 + 1 + 4 + 4 = 13 bytes. Considering that memory blocks are always aligned by 4 bytes, 16 bytes are added.
In summary, the total number of bytes required to create a Sales object instance is 36.
3. Expand 36 bytes of continuous space to the high address on the hosting stack and allocate the memory address to it.
4. initialize the Sales object instance.
A: Construct a Type object of the Sales Type and allocate it to The Loader Heap in the managed Heap.
B: Initialize two additional members of the Sales object instance: TypeHandler and SyncBlockIndex. TypeHandler points to the Method Table on Load Heap, and SyncBlockIndex points to the Synchronization Block memory Block, which is used to synchronize instance objects in a multi-threaded environment.
C: initialization of the Instance field. initialize the instance field of System. Object, then the Employee field, and finally the instance field of Sales.
5. Assign the managed heap address of the Sales instance object to the thread stack Variable _ sales.
□Value type object Creation
For value types (excluding instance members of the class value type), such as struct, an object is actually created on the thread stack using the new keyword. The thread stack is not controlled by CLR garbage collection, it is managed directly by the operating system. When the method of the value-type instance ends, its storage unit is automatically released. On the thread stack, the operating system maintains a stack pointer pointing to the next free space address.
The memory address of the thread stack is filled from the high to the low, that is, the memory address is extended from the high address to the low address when the stack is imported, and the memory address is deleted from the low address to the high address when the stack is exited. Run the following code to go through the entire process:
public void SaySth()
{
int a = 1;
char b = 'x';
}
1. Before method execution, the thread Stack pointer points to a high level, such as 100
2. Start to execute the sayth method. The return address of the sayth method first enters the stack, and the thread Stack pointer points to a lower position, such as 99.
3. Execute int a = 1 and allocate 4 bytes of memory space on the thread stack. Save value 1 in the address space, the thread Stack pointer moves four bytes down to a lower position, for example, 98
4. Execute char B = 'X' and allocate 2 bytes of memory space on the thread stack. Save the value "x" in this address space, the thread Stack pointer moves four bytes down to a lower position, for example, 97
5. The method execution is completed, and the memory of B and a is sequentially performed. The thread Stack pointer returns to the return address, that is, 99.
→ Garbage collection
On the managed stack, a memory space is always reachable and the reference of the object instance is stored, these objects in "GC roots" are marked as "live", and all object instances referenced by "GC root" are also marked as "live ", GC will traverse continuously in this way until an object instance does not reference the object instance. This is like a "node tree", which is traversed from the root node until the node has no subnode cyclically.
During garbage collection, all object instances marked as "live" will be recycled, and the memory space of the managed heap will be compressed again, the low-level memory space of the managed heap is ready for the next batch of instance objects.
"GC root" has different types:
● Local variables in the current method are treated as "GC root"
● Static variables are also treated as "GC root"
● If a hosted object instance needs to be passed to the COM + database, it will also be treated as "GC root"
● If an object has a destructor, it will not be recycled during garbage collection. The object will be recycled only when finalizer is called. That is to say, objects with destructor are also treated as "GC root"
To improve the efficiency of garbage collection, GC introduces a "age-based" policy, which divides the objects in the managed heap into three generations, namely, 0, 1 and 2. In addition, different threshold values will be set for different generations, with 0th generations about 1st kb, 2nd generations about 2 MB, and generations about 10 MB. The first generation of the newly created object is 0th, and GC always recycles the instance of The 0th generation object that is not marked as "live. The specific operation process is as follows:
1. During CLR initialization, all objects added to the managed heap are in generation 0th.
2. When there is garbage collection, the age of unrecycled objects is upgraded to 1 level and changed to 1st generations.
3. If the capacity of the 1st-generation object fails to reach the threshold value, it will not be recycled, which effectively improves the efficiency of garbage collection.
4. Only when the capacity of the 0th generation threshold value is insufficient to create a new object, and the size of the 1st generation object instance exceeds the capacity of the 1st generation threshold value, GC recycles objects of The 0th and 1st generations at the same time. Objects of the 1st generation that have not been recycled are upgraded to 2 generations, and objects of the 0th generation that have not been recycled are upgraded to 1 generation objects.
5. This is repeated.
→ Unmanaged resource cleanup
For some managed resources, the CLR garbage collector can automatically clean up the memory. For some unmanaged resources, such as database connections, file handles, and COM objects, developers still need to manually clean up the memory. For more information, see here.