Abstract:Today we will talk about the garbage collection mechanism in C #. This article will start with the principle of the garbage collection mechanism and hope to help you.
GC's past and present
Although this article uses. NET as the goal to describe GC, the concept of GC was not just born soon. As early as 1958, The LISP Language Implemented by John McCarthy, the famous Tuling prize winner, already provided the GC function, which was the first occurrence of GC. LISPProgramDevelopers think that memory management is too important, so it cannot be managed by programmers themselves.
But in the days that followed, lisp was not a climate, and the language using memory manual management assumed the upper hand, represented by C. For the same reason, different people have different opinions. c Programmers think that memory management is too important to be managed by the system, and sneer at the speed of running the lisp program as a tortoise. Indeed, many people cannot accept the GC speed and the large amount of system resource occupation during the time when every byte was carefully calculated. Later, in 1984, the Smalltalk language developed by Dave Ungar adopted the generational garbage collection technology for the first time (this technology will be discussed below), but Smalltalk has not been widely used.
GC did not appear on the stage of history until the middle of 1990s, thanks to Java's progress. Today's GC is not Wu xiameng. Java uses the Virtual Machine (VM) mechanism to manage program running, including GC management. In the late 1990s S,. Net emerged. Net was managed by CLR (Common Language Runtime) in a way similar to Java. The emergence of these two camps has brought people into the development age based on virtual platforms, and GC has become increasingly popular at this time.
Why is GC used? It can also be said why automatic memory management should be used? There are several reasons:
1. Improved the abstraction of software development;
2. programmers can focus on actual problems without being distracted to manage memory problems;
3. The module interface can be clearer and the coupling between modules can be reduced;
4. The bug caused by improper memory management is greatly reduced;
5. Make memory management more efficient.
In general, GC can help programmers get rid of complicated memory problems, thus improving the speed, quality, and security of software development.
What is GC?
GC, as its name is, is garbage collection. Of course, this is only for memory. Garbage Collector (Garbage Collector, which also becomes GC without confusion) uses the root of the application to traverse all objects dynamically allocated by the application on heap [2], identify whether they are referenced to determine which objects are dead and which still need to be used. The object that is no longer referenced by the root of the application or another object is the dead object, that is, the so-called garbage, which needs to be recycled. This is how GC works. To achieve this principle, there are many types of GCAlgorithm. Common algorithms include reference counting, Mark sweep, and copy collection. Currently, the mainstream virtual systems. Net CLR, Java Vm and rotor both adopt the mark sweep algorithm.
1. Mark-compact mark Compression Algorithm
The. net gc algorithm is regarded as the Mark-compact algorithm. Phase 1: Mark-sweep indicates the clear stage. Assume that all objects in heap can be recycled, find out objects that cannot be recycled, and mark these objects, finally, all the objects not marked in heap can be recycled. In Stage 2: Compact compression, heap memory space becomes discontinuous after the objects are recycled and these objects are moved in heap, this allows them to re-arrange from the heap base address, similar to disk space fragmentation.
After heap memory is recycled and compressed, you can continue to use the heap memory allocation method, that is, you can use only one pointer to record the start address of heap allocation. Main processing steps: suspend the thread → confirm the room → create a reachable objects graph → recycle the object → compress the heap → fix the pointer. It can be understood as follows: The reference relationships of objects in heap are complex (cross-reference, and circular reference) to form a complex graph. roots is a variety of entry points that CLR can find outside heap.
Logsearch by GC includes global objects, static variables, local objects, function call parameters, and object pointers (Finalization queue) in the current CPU register. It can be classified into two types: initialized static variables and the objects (stack + CPU register) that the thread is still using ). Reachable objects: refers to the objects that can be reached from the roots based on the object reference relationship. For example, if the local variable object A of the current function is a root object and its member variable references object B, B is a reachable object. You can create a reachable objects graph from the roots. The remaining objects are unreachable and can be recycled.
The pointer is fixed because the Compact process moves the heap object and the object address changes. You need to fix all reference pointers, including the pointer in stack, CPU register, and the reference pointer of other objects in heap. There is a slight difference between debug and release execution modes.CodeObjects that are not referenced are unreachable. In debug mode, you must wait until the current function is executed to make these objects unreachable. The purpose is to track the content of a local object during debugging. The hosted object passed to COM + will also become root, and has a reference counter to be compatible with the memory management mechanism of COM +. When the reference counter is 0, these objects can be recycled objects. Pinned objects refers to objects that cannot be moved after allocation, for example, objects passed to unmanaged code (or objects that use the fixed keyword ), GC cannot modify the reference pointer in the unmanaged code when the pointer is fixed, so moving these objects will cause an exception. Pinned objects can cause heap fragments, but in most cases, objects passed to unmanaged code should be reclaimed during GC.
Ii. Generational generational Algorithms
The program may use several hundred MB or several GB of memory to perform GC operations on such memory areas. The generation algorithm has a certain statistical basis, which significantly improves GC performance. Objects are divided into new and old objects according to their lifecycles. Different recycling policies and algorithms can be used for new and old regions based on the results of statistical distribution rules, strengthen the recycling of new regions, and strive for a shorter interval and smaller memory areas, at a lower cost, a large number of recently discarded local objects in the execution path are recycled in a timely manner. Preconditions for the generational algorithm:
1. A large number of newly created objects have shorter lifecycles, while older objects have longer lifecycles;
2. reclaim part of the memory is faster than reclaim all the memory;
3. Newly created objects are usually highly correlated. The heap allocation objects are continuous, and the correlation degree is strong to improve the CPU cache hit rate.. Net divides the heap into three age-based regions: Gen 0, Gen 1, and Gen 2;
Heap is divided into three age-based regions. There are three GC Methods: # Gen 0 collections, # Gen 1 collections, # Gen 2 collections. If Gen 0 heap memory reaches the threshold, the 0 generation GC is triggered. After the 0 generation GC, the surviving objects in Gen 0 enter gen1. If the memory of Gen 1 reaches the threshold value, 1 generation GC is performed. 1 generation GC recycles Gen 0 heap and Gen 1 heap together, and the surviving object enters gen2.
The two generations of GC collect Gen 0 heap, Gen 1 heap and Gen 2 heap together. gen 0 and Gen 1 are relatively small, and the age of these two generations is always around 16 Mb; the size of gen2 is determined by the application and may reach several GB. Therefore, the cost of the 0-and 1-generation GC is very low, and the 2-generation GC is called full GC, which is usually very high. A rough calculation of 0-and 1-generation GC should be completed in milliseconds to dozens of milliseconds. When Gen 2 heap is large, full GC may take several seconds. In general, the GC frequency of generation 2, Generation 1, and generation 0 should be roughly during. NET application running.
3. Finalization queue and freachable queue
The two queues are related to the Finalize method provided by the. NET object. These two queues are not used to store real objects, but to store a group of pointers to objects. When the new operator is used in the program to allocate space on the managed heap, GC will analyze it, if the object contains the Finalize method, add a pointer to the object in the Finalization queue.
After GC is started, the mark stage is used to identify the garbage. Search in the spam. If any pointer in the Finalization queue points to the object in the spam, the object will be separated from the spam, and move the pointer to it to freachable queue. This process is called the resurrection of an object. The dead object is saved. Why do we need to save it? Because the Finalize method of this object has not been executed, it cannot be killed. Freachable queue does nothing at ordinary times, but once a pointer is added in it, it will trigger the Finalize method execution of the indicated object, and then remove the pointer from the queue, this is the object that can die quietly.
The. NET Framework system. GC class provides two methods to control finalize, reregisterforfinalize and suppressfinalize. The former is the Finalize method that requests the system to complete the object, and the latter is the Finalize method that requests the system not to complete the object. The reregisterforfinalize method is to re-Add the pointer to the object to the Finalization queue. This is a very interesting phenomenon, because the objects in the Finalization queue can be re-generated. If you call the reregisterforfinalize method in the Finalize method of the object, in this way, an object that will never die on the stack is formed. Like the Phoenix Nirvana, it can be resumed every time it dies.
Managed resources:
All types in. NET are (directly or indirectly) derived from the system. Object type.
The type in CTS is divided into two categories: reference type (managed type), which is allocated to the memory stack; Value Type ), allocated on the stack.
The value type is in the stack. When it comes out first, the Life Sequence of the value type variable is sequential. This ensures that the value type variable will release resources before exiting the scope. It is simpler and more efficient than the reference type. The stack allocates memory from the high address to the low address.
The reference type is assigned to the managed heap, and a variable is declared to be saved on the stack. when an object is created using new, the object address is stored in the variable. Instead, the managed heap allocates memory from the low address to the high address,
Over 80% of. net resources are managed resources.
Unmanaged resources:
Applicationcontext, brush, component, componentdesigner, container, context, cursor, filestream, Font, icon, image, matrix, object, odbcdatareader, oledbdatareader, pen, RegEx, socket, streamwriter, timer, tooltip, file handle, GDI resource, database connection, and other resources. It is possible that many of them did not notice it during use!
The gc mechanism of. Net has two problems:
First, GC does not release all resources. It cannot automatically release unmanaged resources.
Second, GC is not real-time, which will cause bottlenecks and uncertainties in system performance.
GC is not real-time, which may cause bottlenecks and uncertainty in system performance. With the idisposable interface, the idisposable interface defines the dispose method, which is used by programmers to explicitly call to release unmanaged resources. You can use using statements to simplify resource management.
Example:
/// Summary /// execute the SQL statement, returned Number of affected records /// summary /// Param name = "sqlstring" SQL statement/Param // number of records affected by returns/returns publicstaticint executesql (string sqlstring) {using (sqlconnection connection = new sqlconnection (connectionstring) {using (sqlcommand cmd = new sqlcommand (sqlstring, connection) {try {connection. open (); int rows = cmd. executenonquery (); Return rows;} catch (system. data. sqlclient. sqlexception e) {connection. close (); throw E;} finally {cmd. dispose (); connection. close ();}}}}
When you use the dispose method to release an unmanaged object, you should call GC. suppressfinalize. If the object is in the Finalization queue, GC. suppressfinalize will prevent GC from calling the Finalize method. Because the call of the Finalize method sacrifices some performance. If your dispose method has cleared the delegate manager resources, you do not need to have GC call the Finalize method (msdn) of the object ). The msdn code is attached for your reference.
Publicclass baseresource: idisposable {// point to external unmanaged resources private intptr handle; // other managed resources used by this class. private component components; // specifies whether to call the component. the dispose method, identification space, controls the behavior of the garbage collector privatebool disposed = false; // constructor public baseresource () {// insert appropriate constructor code here .} // implement the interface idisposable. // cannot be declared as virtual. // The subclass cannot override this method. publicvoid dispose () {dispose (true); // exit the Finalization queue of the end queue // sets the object's blocking Terminator generation Code // GC. suppressfinalize (this);} // The execution of dispose (bool disposing) is divided into two different situations. // If disposing is equal to true, the method has been called // or indirectly called by user code. both managed and unmanaged code can be released. // If disposing is set to false, the method has been called by the finalizer internally. // you cannot reference other objects, only unmanaged resources can be released. Protectedvirtualvoid dispose (bool disposing) {// check whether the dispose has been called. If (! This. disposed) {// if true, release all managed and unmanaged resources if (disposing) {// release managed resources. components. dispose ();} // release an unmanaged resource. If disposing is false, // only the following code is executed. closehandle (handle); handle = intptr. zero; // note that this is not thread-safe. // After the managed resources are released, other threads can be started to destroy objects. // However, before the disposed mark is set to true, the client must implement thread security.} Disposed = true;} // use InterOP to call the method // clear unmanaged resources. [system. runtime. interopservices. dllimport ("Kernel32")] privateexternstatic Boolean closehandle (intptr handle ); // use the C # destructor to implement the final code // This can be called and executed only when the dispose method is not called. // If you give the opportunity to terminate the base class, // do not provide the destructor to the subclass .~ Baseresource () {// do not duplicate the cleanup code. // based on reliability and maintainability, calling dispose (false) is the best way to call dispose (false);} // you can call the dispose method multiple times, // but an exception is thrown if the object has been released. // No matter when you process the object, check whether the object is released. // check to see if it has been disposed. publicvoid dosomething () {If (this. disposed) {thrownew objectdisposedexception () ;}// do not set the method to virtual. // The Inheritance class cannot override this method publicvoid close () {// No parameter calls the dispose parameter. dispose ();} publicstaticvoid main () {// insert code here to create // and use a baseresource object .}}
GC. Collect () method
Purpose: Force garbage collection.
GC method:
| name |
description |
| collect () |
forces instant garbage collection for all generations. |
| collect (int32) |
force the zero-to-specified-generation garbage collection feature. |
Collect (int32, gccollectionmode) |
Forces garbage collection from zero to specified generations at the time specified by the gccollectionmode Value |
GC considerations:
1. users need to manage only memory and unmanaged resources, such as file handles, GDI resources, and database connections.
2. Implementation of circular references and mesh structures will become simple. GC flag-the compression algorithm can effectively detect these relationships and delete the entire referenced mesh structure.
3. GC traverses from the root object of the program to check whether an object can be accessed by other objects, rather than using reference counting methods similar to that in COM.
4. GC runs in an independent thread to delete memory that is no longer referenced.
5. GC compresses the managed heap every time it runs.
6. You must be responsible for the release of unmanaged resources. You can define finalizer in the type to ensure that resources are released.
7. The execution time of the finalizer of an object is an uncertain time after the object is no longer referenced. Note that it is not the same as in C ++ that the Destructor is executed immediately when the object exceeds the declared period.
8. The use of finalizer has a performance cost. Objects requiring finalization will not be cleared immediately. Therefore, you must first execute finalizer. finalizer, instead of being called in the GC thread. GC puts every object that needs to execute finalizer in one queue, then starts another thread to execute all these finalizers, And the GC thread continues to delete other objects to be recycled. In the next GC cycle, the memory of these finalizer objects will be recycled.
9.. Net GC uses the concept of "Generation" (generations) to optimize performance. Generation helps GC more quickly identify objects that are most likely to become spam. After the garbage collection is executed last time, the newly created object is the 0th generation object. The object that has undergone a GC cycle is a 1st-generation object. Objects that have gone through two or more GC cycles are 2nd generation objects. The role of substitution is to distinguish between local variables and objects that need to survive throughout the application lifecycle. Most 0th-generation objects are local variables. Member variables and global variables quickly become 1st-generation objects and eventually become 2nd-generation objects.
10. GC executes different check policies for different generations of objects to optimize performance. Each GC cycle checks the 0th generation object. About 1/10 of GC cycle checks for 0th generation and 1st generation objects. About 1/100 of GC cycle checks all objects. Rethink the finalization cost: objects requiring finalization may stay in the memory for an additional nine GC cycles than those without finalization. If it is not finalize yet, it will become a 2nd-generation object, thus staying in the memory for a longer time.