Section 1 in the early C/C ++ development of the garbage collection mechanism, the lifecycle of an object is like this: computing the object size-finding available memory-initializing the object-using the object-destroying the object. If the developer forgets the "Destroy object" step during the above process, it is likely to cause memory leakage! This is a terrible thing! Fortunately, CLR developers solve this problem for us.. NET Framework introduces the garbage collection mechanism, so that developers do not need to pay more attention to the memory release issue. CLR will execute garbage collection when appropriate to release memory that is no longer used. Here is what an evil man said: Give me a woman, and I can create a nation! In fact, you can create a new world only if you have enough planet memory to accommodate your children! That's what CLR thinks.
When a process is activated, CLR retains a continuous memory. During the main thread startup, a series of objects may be initialized, CLR first calculates the object size and the number of bytes occupied by its overhead, and then allocates memory for these objects in consecutive memory blocks. These objects are configured in 0th generations of memory, when constructing the 0th-generation memory, a default memory size will be allocated. As the program runs, more objects may be initialized, CLR finds that the 0th-generation memory cannot load more new objects. At this time, the CLR starts the Garbage Collector to recycle the 0th-generation memory, and the memory occupied by the unused objects will be released, then, the 0-generation object is upgraded to the 1st-generation object, and the new object is configured in The 0th-generation memory zone. CLR uses three stages of generation. Each time the newly allocated object is configured in The 0th generation memory, the oldest object is in the 2nd generation memory. Every time a new object is allocated with memory, all of them may be garbage collection to release the memory. Obviously, the CLR thinks that "the memory will never be used up". Obviously, the CLR automatically manages the memory garbage for us, obviously, the "think" of CLR is not true for our developers. We will explain the garbage collection mechanism from the following aspects.
Section 2 garbage collection for memory allocation is of reference type.
CLR requires that the reference type objects allocate memory from the managed heap. The value type is to allocate memory from the stack. In C #, we usually use the new operator to create an object. The compiler will generate the newobj command in IL and execute a newobj command in the following process: (as we know in the previous section, A continuous memory block is reserved when A process starts. The number of bytes required for fields of the computing type and its base type is, calculate the pointer of the type object and A synchronized index block in 8 or 16 bytes. By now, the total memory (A + 8 or 18) bytes is required, CLR checks whether the current process zone has enough memory to hold (A + 8 or 16) bytes of objects. If yes, it places the new objects in the zone, otherwise, CLR recycles garbage and releases memory that is no longer in use to accommodate new objects. throughout the lifecycle of the Process, CLR maintains a pointer P, it always points to the end of the last Object Memory allocated by the current process and does not run out of the current process memory boundary,
When calculating the number of bytes required for the new object to be created, CLR checks the available memory zone by adding P and the new number of bytes. If the value exceeds the end of the address, it indicates that the current managed heap has been used up and is ready for garbage collection. Since the process has an independent and continuous memory zone, CLR can ensure that the new objects created are basically placed next to each other.
In section 3, when the memory of the managed heap is used up and new objects are nowhere to be placed, the CLR starts to recycle garbage. As the program continues to run, the managed heap may become larger and larger, if you want to recycle the whole managed heap (The following describes how to recycle it), it will inevitably seriously affect performance, because sometimes it may take dozens of bytes to accommodate new objects, sometimes it may be necessary to relocate reachable objects. In order to perform garbage collection in a small range with a specific purpose, CLR uses the concept of "Generation" to optimize the garbage collector, generation is a logical technology used by the garbage collection mechanism and an algorithm. It divides the memory in the managed heap into three generations (until now. NET Framework4.0 has three generations: 0, 1, and 2 ).
During initialization, the CLR initializes the managed heap as a memory area containing 0 objects, and the newly added objects in the heap are 0th generation objects, the CLR allocates a default quota when initializing the 0th-generation memory zone, which is assumed to be 512 KB.. NET Framework and version. This quota may be different. Assume that the process and its threads are allocated with four objects after initialization, for example:
These four objects occupy 5th K of memory, and the program continues to run. When the first object Obj5 is reallocated, The 0th generation has no available memory, in this case, the CLR starts the Garbage Collector for garbage collection. If the above Obj3 is invalid, the memory of Obj3 will be released, next, move the Obj4 object to the Obj3 location (at the end of the Obj2 memory address). The surviving objects Obj1, Obj2, and Obj4 will be upgraded to the 1st generation object, based on the program running status, the 1st-generation memory area may be allocated with a memory size of 20 MB (or other values). The 0th-generation memory is temporarily empty, next, allocate Obj5 To The 0th generation memory zone, as shown below:
The program continues to run, and a new allocation of four object Obj6-Obj9, And now Obj2 and Obj5 are no longer used, that is, the object is not reachable, at this time you need to create a new object Obj10, however, The 0th-generation 0th K memory has been used up, so the CLR once again starts the Garbage Collector for garbage collection, this time the Garbage Collector will think that the-generation new object has a short life cycle, so first, we need to recycle the 0th generation and raise the surviving object to the 1st generation. The garbage collector finds that the objects in the 1st generation are much less than 20 M at this time, so it gives up the 1st generation recycling, the program continues to run and allocates N more new objects. When the 0th generation object is upgraded to the 1st generation object, and the 1st generation object exceeds 20 m, the 1st generation object is recycled, the 1st-generation surviving objects are upgraded to the 2nd-generation object, and The 0th-generation surviving objects are upgraded to the 1st-generation object, for example:
During each garbage collection process, the garbage collector automatically adjusts the default quota of 0th, 1, and 2 generations according to actual usage. For example, the default quota of 2nd generations may be adjusted to 200 MB, after a few minutes, it may be adjusted to 120 M, or 1024 M. The program continues to run. After three items are reclaimed and the quota is adjusted again, if the available memory is not enough to place new objects, the CLR will throw an OutOfMemoryException, and the living gods cannot be rescued. Previously, CLR thought that "memory is never used up" is also conditional!
Section 4: The garbage collection process hosts an object in the heap. When a variable in the thread references the object, the object is reachable. Otherwise, the object is inaccessible.
At the beginning of a garbage collection process, the garbage collector considers all objects in the heap as garbage.
The first step is to mark the object. The Garbage Collector checks all the roots along the thread stack. static fields, method parameters, local variables in the activity, and objects pointed to by registers are all the roots, when A root references the managed heap object A, the Garbage Collector marks object A. When marking object A, if another object B is referenced in object, B is also marked. After a root check is completed, the next root is detected and the same marking process is executed. In the code, multiple objects may reference the same object C, as long as the Garbage Collector detects that object C has been marked, it no longer detects the objects referenced in Object C to prevent infinite loop marking. A marked object is an reachable object, and an unmarked object is an inaccessible object.
The second step is to move the object to the compression heap. The Garbage Collector traverses all objects in the heap to find unmarked objects. Because unmarked objects are spam objects, they can be recycled. If a small object is found, otherwise, the memory occupied by these spam objects will be released, and the reachable objects will be moved here to compress the heap. After the reachable objects are relocated, all variables pointing to these objects will be invalid, the garbage collector then traverses all the roots of the application to modify their references. In this process, if each thread is executing, it is likely that the variable is referenced to an invalid object address. Therefore, the thread of the entire process that is executing the managed code is suspended.
In fact, when the garbage collector is preparing to start a collection, all threads that are executing managed code must be suspended, the CLR records the instruction pointer of each thread to determine where the thread is currently executed so that it can be restored after the garbage collection ends in the future. If the instruction pointer of a thread just reaches a security point, the thread can be suspended. Otherwise, the CLR will attempt to hijack the thread. If the thread has not reached a security point, then, after several hundred milliseconds, the CLR will attempt to hijack the thread again. It is possible that the thread will be suspended after several attempts, when all the threads that execute the hosted code of the current process are suspended, the garbage collector can start to work. (You can find related information about thread hijacking ). After the garbage collector is recycled, CLR restores all threads and the program continues to run. It can be seen that garbage collection has a huge impact on performance!
Section 5 when a large object is created, any object larger than or equal to 85000 bytes is considered as a large object, and the memory of these objects is allocated from the large object heap, large objects are always regarded as 2nd-generation objects. To improve performance, the garbage collector should avoid allocating large objects to reduce performance damage, only 2nd generations of memory are recycled.
Section 6 manual garbage collection in general, CLR will intelligently collect garbage when necessary, but we can also manually start the Garbage Collector, System. the GC class provides a static method for restarting the version to start the Garbage Collector:
// Collect garbage from all generations.
GC. Collect ();
// Recycle the specified generation of garbage.
GC. Collect (int generation );
// Force the time specified by the System. GCCollectionMode value to recycle the garbage from zero generation to the specified generation.
GC. Collect (int generation, GCCollectionMode mode );
As we know in the previous section, every garbage collection process will cause performance damage, so we try to avoid calling these three methods for garbage collection. Of course, we can also call them if necessary.
Not only will the Garbage Collector be started in the above situations, but when the CLR receives a memory emergency notification from Windwos, it will also start garbage collection, and the CLR will also start garbage collection when uninstalling AppDomain.
Instance
The Code is as follows: |
Copy code |
// File: MyClass. cs Using System; Using System. Collections. Generic; Using System. Text;
Namespace ConsoleApplication2 { Class MyClass { ~ MyClass () { Console. WriteLine ("In MyClass destructor ++ "); } } } // File: MyAnotherClass. cs Using System; Using System. Collections. Generic; Using System. Text;
Namespace ConsoleApplication2 { Public class MyAnotherClass { ~ MyAnotherClass () { Console. WriteLine ("In MyAnotherClass destructor ___________________________________"); } } } // File: Program. cs Using System; Using System. Collections. Generic; Using System. Text;
Namespace ConsoleApplication2 { Class Program { Static void Main (string [] args) { MyClass myClass = new MyClass (); MyAnotherClass myAnotherClass = new MyAnotherClass (); WeakReference mydomainweakreferenceobject = new WeakReference (myClass ); WeakReference myLongWeakReferenceObject = new WeakReference (myAnotherClass, true ); Console. WriteLine ("Release managed resources by setting locals to null ."); MyClass = null; MyAnotherClass = null;
Console. WriteLine ("Check whether the objects are still alive ."); CheckStatus (mydomainweakreferenceobject, "myClass", "mydomainweakreferenceobject "); CheckStatus (myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject ");
Console. WriteLine ("Programmatically cause GC ."); GC. Collect ();
Console. WriteLine ("Wait for GC runs the finalization methods ."); GC. WaitForPendingFinalizers ();
// Check whether the objects are still alive. CheckStatus (mydomainweakreferenceobject, "myClass", "mydomainweakreferenceobject "); CheckStatus (myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject ");
Console. WriteLine ("Programmatically cause GC again. Let's see what will happen this time ."); GC. Collect ();
// Check whether the objects are still alive. CheckStatus (mydomainweakreferenceobject, "myClass", "mydomainweakreferenceobject "); CheckStatus (myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject ");
MyAnotherClass = (MyAnotherClass) myLongWeakReferenceObject. Target;
Console. ReadLine (); }
Static void CheckStatus (WeakReference weakObject, string strLocalVariableName, string strWeakObjectName) { Console. WriteLine (strLocalVariableName + (weakObject. IsAlive? "Is still alive.": "is not alive .")); Console. WriteLine (strWeakObjectName + (weakObject. Target! = Null? ". Target is not null.": ". Target is null .")); Console. WriteLine (); } } } // File: MyClass. cs Using System; Using System. Collections. Generic; Using System. Text; Namespace ConsoleApplication2 { Class MyClass { ~ MyClass () { Console. WriteLine ("In MyClass destructor ++ "); } } } // File: MyAnotherClass. cs Using System; Using System. Collections. Generic; Using System. Text; Namespace ConsoleApplication2 { Public class MyAnotherClass { ~ MyAnotherClass () { Console. WriteLine ("In MyAnotherClass destructor ___________________________________"); } } } // File: Program. cs Using System; Using System. Collections. Generic; Using System. Text; Namespace ConsoleApplication2 { Class Program { Static void Main (string [] args) { MyClass myClass = new MyClass (); MyAnotherClass myAnotherClass = new MyAnotherClass (); WeakReference mydomainweakreferenceobject = new WeakReference (myClass ); WeakReference myLongWeakReferenceObject = new WeakReference (myAnotherClass, true ); Console. WriteLine ("Release managed resources by setting locals to null ."); MyClass = null; MyAnotherClass = null; Console. WriteLine ("Check whether the objects are still alive ."); CheckStatus (mydomainweakreferenceobject, "myClass", "mydomainweakreferenceobject "); CheckStatus (myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject "); Console. WriteLine ("Programmatically cause GC ."); GC. Collect (); Console. WriteLine ("Wait for GC runs the finalization methods ."); GC. WaitForPendingFinalizers (); // Check whether the objects are still alive. CheckStatus (mydomainweakreferenceobject, "myClass", "mydomainweakreferenceobject "); CheckStatus (myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject "); Console. WriteLine ("Programmatically cause GC again. Let's see what will happen this time ."); GC. Collect (); // Check whether the objects are still alive. CheckStatus (mydomainweakreferenceobject, "myClass", "mydomainweakreferenceobject "); CheckStatus (myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject "); MyAnotherClass = (MyAnotherClass) myLongWeakReferenceObject. Target; Console. ReadLine (); } Static void CheckStatus (WeakReference weakObject, string strLocalVariableName, string strWeakObjectName) { Console. WriteLine (strLocalVariableName + (weakObject. IsAlive? "Is still alive.": "is not alive .")); Console. WriteLine (strWeakObjectName + (weakObject. Target! = Null? ". Target is not null.": ". Target is null .")); Console. WriteLine (); } } } |
Garbage collection mechanism key points
1. ASP.. NET resources are divided into managed resources and non-managed resources. For managed resources ,. ASP.. net gc can well recycle useless garbage, but manual garbage removal (explicit release) is required for unmanaged (such as file access and network access ).
2. ASP. NET provides two methods to release unmanaged resources:
2-1.finalizer: It looks like a C ++ destructor, but in essence it is quite different. Finalizer is the terminator called before the object is recycled by GC. The original intention is to release the unmanaged resources here. However, due to the uncertainty of the GC runtime, the release of unmanaged resources is usually delayed. In addition, Finalizer may have unexpected side effects. For example, the recycled object has not been referenced by other available objects, but Finalizer has made it available again, this damages the atomicity of the GC garbage collection process and increases the GC overhead.
2-2.Dispose mode: C # provides the using keyword to support Dispose Pattern for resource release. In this way, unmanaged resources can be released in a definite way, and the using structure provides exceptional security. Therefore, we generally recommend that you use Dispose Pattern and check it in Finalizer. If you forget the explicit Dispose object, you can release the resource in Finalizer.
3. Reclaim managed resources to determine whether the object is to be recycled. It is valid to determine whether the object or its contained Sub-objects have no reference.
4. GC cost: first, the real-time recovery of managed resources is lost. Second, the management of C # managed resources and non-managed resources is not unified, which leads to concept separation.
5. ASP. NET type is divided into two categories: reference type and value type. The value type is assigned to the stack, and GC is not required. The reference type is allocated to the stack. GC is required for its release and recovery. To Recycle an object of the reference type, it must be junk.
6. The system arranges independent threads for GC, and takes a certain priority algorithm for memory recycle GC to round-robin and recycle memory resources.
7. Generation (Generation): In order to improve the performance, the older the object will survive the longer. ASP. NET is generally divided into three generations.
When to recycle it?
The garbage collector periodically cleans up the memory. Generally, the garbage collector starts in the following cases:
(1) When memory overflow is insufficient, it should be more accurate to say that the 0th generation object is full.
(2) Call the GC. Collect method to force garbage collection.
(3) When Windows reports that the memory is insufficient, CLR enforces garbage collection.
(4) When CLR unmounts the AppDomain, GC will recycle all the older objects.
(5) In other cases, such as insufficient physical memory, exceeding the memory segment threshold of the short-term survival generation, and the running host rejects memory allocation.