Original: http://www.mikel.cn/article.asp?id=1698
Do you know the garbage collection mechanism of .Net? Can you briefly describe how GC works? How can we effectively manage memory? What is the role of the object instantiated in the body of the Using statement?
This section is organized as follows, 1. Net types and memory allocation 2. How the GC garbage collector works 3. What are unmanaged resources 4. How to effectively release object resources. Summary. Let’s start our study of this section now.
1..Net types and memory allocation
All types in Net are derived (directly or indirectly) from the System.Object type.
Types in CTS are divided into two categories - reference types (reference types, also called managed types), which are allocated on the memory heap, and value types. Value types are allocated on the stack. As shown in the picture
Value types are on the stack, first in, last out. Value type variables have an order of life. This ensures that value type variables will release resources before they are pushed out of scope. Simpler and more efficient than reference types. The stack allocates memory from high addresses to low addresses.
The reference type is allocated on the managed heap (Managed Heap), and a variable is declared and saved on the stack. When new is used to create an object, the address of the object is stored in this variable. On the contrary, the managed heap allocates memory from low addresses to high addresses, as shown in the figure
2. How the GC garbage collector works
In the above figure, when the dataSet expires, we do not display the destruction of the object, and the objects on the heap continue to exist, waiting for GC recycling.
It is recommended but not required that the garbage collector supports object aging through generation. A generation is a unit of objects with relative ages in memory. Object
The codename or age identifies the generation to which the object belongs. In the life cycle of the application, objects created more recently belong to a newer generation and have higher values than objects created earlier.
Lower sub-code number. The object code in the most recent generation is 0.
When creating a new object, you must first search the free linked list to find the most suitable memory block, allocate it, adjust the memory block linked list, and merge fragments. The new operation can be completed in almost O(1) time, adding 1 to the top pointer of the heap. The working principle is: When the remaining space on the managed heap is insufficient, or the space of Generator 0 is full, the GC runs and starts to reclaim memory. At the beginning of garbage collection, the GC compresses and adjusts the heap memory, and objects are concentrated at the top. GC will occupy a certain amount of CPU time when scanning garbage. The original GC algorithm really scans the entire heap, which is inefficient. The current GC divides the objects in the heap into three generations. The most recent entry into the heap is generation 0, followed by generation 1 and generation 2. The first GC only scans generation 0. If the reclaimed space is enough for current use, there is no need to scan objects in other generations. Therefore, GC creates objects more efficiently than C++ and does not need to scan the entire heap space. The performance improvement brought by the scanning strategy and the memory management strategy is enough to compensate for the CPU time occupied by the GC.
3. What are unmanaged resources?
A common unmanaged resource is an object that wraps an operating system resource, such as a file, window, or network connection. For such resources, although the garbage collector can track the lifetime of the object that wraps the unmanaged resource, it knows how to clean up these resources. Fortunately, the Finalize() method provided by the .net Framework allows unmanaged resources to be properly cleaned up before the garbage collector recycles such resources. Here are several common unmanaged resources: brushes, stream objects, component objects and other resources (Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, ApplicationContext, Brush,
Component,ComponentDesigner,Container,Context,Cursor,FileStream,
Font,Icon,Image,Matrix,Timer,Tooltip). (Refer to MSDN)
4. How to effectively release unmanaged resources.
GC cannot manage unmanaged resources, so how to release unmanaged resources? .Net provides two methods:
(1) Destructor: When the garbage collector recycles the resources of an unmanaged object, it will call the object's finalization method Finalize() to clean up the resources. However, due to the limitations of GC working rules, the GC calls the object's Finalize method. The resource will not be released once, and the object will be deleted after the second call.
(2) Inherit the IDisposable interface and implement the Dispose() method. The IDisposable interface defines a pattern (with language-level support), provides a certain mechanism for releasing unmanaged resources, and avoids the inherent problems with garbage collection of destructors. Device-related issues.
In order to better understand the garbage collection mechanism, I specially wrote part of the code and added detailed comments. Define a single class FrankClassWithDispose (inherits the interface IDisposable), FrankClassNoFinalize (no finalizer), FrankClassWithDestructor (defines the destructor).
The specific code is as follows:
Code
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Data;
5 using System.Data.Odbc;
6 using System.Drawing;
7 // Coded By Frank Xu Lei 18/2/2009
8 // Study the .NET Memory Management
9 // Garbage Collector garbage collector. Hosted resources can be reclaimed when needed according to policies,
10 // But the GC doesn't know how to manage unmanaged resources. Such as network connection, database connection, brush, component, etc.
11 //Two mechanisms to solve the problem of releasing unmanaged resources. Destructor, IDispose interface
12 // COM reference count
13 // C++ manual management, New Delete
14 // VB automatic management
15 namespace MemoryManagement
16 {
17 // Inherit the interface IDisposable, implement the Dispose method, and release the instance resources of FrankClassDispose
18 public class FrankClassWithDispose : IDisposable
19 {
20 private OdbcConnection _odbcConnection = null;
twenty one
22 // Constructor
23 public FrankClassWithDispose()
twenty four {
25 if (_odbcConnection == null )
26 _odbcConnection = new OdbcConnection();
27 Console.WriteLine( " FrankClassWithDispose has been created " );
28 }
29 //Test method
30 public void DoSomething()
31 {
32
33 /**/ /// /code here to do something
34 return;
35}
36 // Implement Dispose and release the resources used by this class
37 public void Dispose()
38 {
39 if (_odbcConnection != null )
40 _odbcConnection.Dispose();
41 Console.WriteLine( " FrankClassWithDispose has been disposed " );
42 }
43}
44 // Finalize is not implemented, wait for GC to recycle the instance resources of FrankClassFinalize, and recycle them directly when GC is running.
45 public class FrankClassNoFinalize
46 {
47 private OdbcConnection _odbcConnection = null ;
48 //Constructor
49 public FrankClassNoFinalize()
50 {
51 if (_odbcConnection == null )
52 _odbcConnection = new OdbcConnection();
53 Console.WriteLine( " FrankClassNoFinalize has been created " );
54 }
55 //Test method
56 public void DoSomething()
57 {
58
59 // GC.Collect();
60 /**/ /// /code here to do something
61 return;
62 }
63}
64 // Implement the destructor, compile it into the Finalize method, and call the object's destructor
65 // When GC is running, the resource is not released in the first call but only in the second call.
66 //Instance resources of FrankClassDestructor
67 // The CLR uses an independent thread to execute the Finalize method of the object. Frequent calls will degrade performance.
68 public class FrankClassWithDestructor
69 {
70 private OdbcConnection _odbcConnection = null ;
71 //Constructor
72 public FrankClassWithDestructor()
73 {
74 if (_odbcConnection == null )
75 _odbcConnection = new OdbcConnection();
76 Console.WriteLine( " FrankClassWithDestructor has been created " );
77 }
78 //Test method
79 public void DoSomething()
80 {
81 /**/ /// /code here to do something
82
83 return ;
84}
85 // Destructor, releases unmanaged resources
86 ~ FrankClassWithDestructor()
87 {
88 if (_odbcConnection != null )
89 _odbcConnection.Dispose();
90 Console.WriteLine( " FrankClassWithDestructor has been disposed " );
91 }
92 }
93}
94
An instance of the unmanaged object OdbcConnection is used. The built client was briefly tested. The client code is as follows:
Code
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Data;
5 using MemoryManagement;
6 // Coded By Frank Xu Lei 18/2/2009
7 // Study the .NET Memory Management
8 // Test The Unmanaged Objects Reclaimed.
9 // Tests for unmanaged code, comparison
10 // For managed code, GC can recycle it by itself more strategically, or it can implement IDisposable, call the Dispose() method, and actively release it.
11 namespace MemoryManagementClient
12 {
13 class programs
14 {
15 static void Main( string [] args)
16 {
17
18 /**/ ///////////////////////////////////////// //(1) / //////////////////////////////////////////// //
19 //Call the Dispose() method to actively release. resources, flexibility
20 FrankClassWithDispose _frankClassWithDispose = null ;
21 try
twenty two {
23 _frankClassWithDispose = new FrankClassWithDispose();
24 _frankClassWithDispose.DoSomething();
25
26}
27 finally
28 {
29 if (_frankClassWithDispose != null )
30 _frankClassWithDispose.Dispose();
31 // Console.WriteLine("FrankClassWithDispose instance has been released");
32}
33
34 /**/ ///////////////////////////////////////// //(2) / /////////////////////////////////////////////// /
35 // You can use the Using statement to create an unmanaged object. Before the method execution ends, it will be called
36 using (FrankClassWithDispose _frankClassWithDispose2 = new FrankClassWithDispose())
37 {
38 // _frankClassWithDispose2.DoSomething();
39 }
40
41 /**/ ///////////////////////////////////////// //(3) / //////////////////////////////////////////// //
42 //When the garbage collector is running, resources are released once
43 FrankClassNoFinalize _frankClassNoFinalize = new FrankClassNoFinalize();
44 _frankClassNoFinalize.DoSomething();
45
46 /**/ ///////////////////////////////////////////// (4) //////////////////////////////////////////////// /
47 // When the garbage collector is running, it takes two times to release resources.
48 FrankClassWithDestructor _frankClassWithDestructor = new FrankClassWithDestructor();
49 _frankClassWithDestructor.DoSomething();
50 /**/ //////////////////////////////////////////// /(5 ) ////////////////////////////////////////////////
51 // The Using statement cannot be used to create an object because it does not implement the IDispose interface.
52 // using (FrankClassWithDestructor _frankClassWithDestructor2 = new FrankClassWithDestructor())
53 // {
54 // _frankClassWithDestructor2.DoSomething();
55 // }
56
57 /**/ //////////////////////////////////////////////// ////////////////////////////////////////// //
58 // For Debug
59 Console.WriteLine( " Press any key to continue " );
60 Console.ReadLine();
61
62
63}
64}
65 }
66
Sometimes resources must be released at a specific time. A class can implement the interface IDisposable that performs resource management and cleanup task methods IDisposable.Dispose.
If the caller needs to call the Dispose method to clean up the object, the class must implement the Dispose method as part of the contract. The garbage collector does not call by default
Dispose method; however, implementing the Dispose method can call methods in the GC to regulate the final behavior of the garbage collector.
It is worth mentioning that: calling the Dispose() method actively releases resources and is flexible. You can use the Using statement to create unmanaged objects. Before the method execution ends, it will be called
The Dispose() method releases resources. The effect of the two ends of the code is the same. You can view the compiled IL.
Code
1. try
2 {
3 IL_0003: nop
4 IL_0004: newobj instance void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
5 IL_0009: stloc. 0
6 IL_000a: ldloc. 0
7 IL_000b: callvirt instance void [MemoryManagement]MemoryManagement.FrankClassWithDispose::DoSomething()
8 IL_0010: nop
9 IL_0011: nop
10 IL_0012: leave.s IL_0028
11 } // end .try
12 finally
13 {
14 IL_0014: nop
15 IL_0015: ldloc. 0
16 IL_0016: ldnull
17 IL_0017: ceq
18 IL_0019: stloc.s CS$ 4 $ 0000
19 IL_001b: ldloc.s CS$ 4 $ 0000
20 IL_001d: brtrue.s IL_0026
21 IL_001f: ldloc. 0
22 IL_0020: callvirt instance void [MemoryManagement]MemoryManagement.FrankClassWithDispose::Dispose()
23 IL_0025: nop
24 IL_0026: nop
25 IL_0027: endfinally
26 } // end handler
27 IL_0028: nop
28 IL_0029: newobj instance void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
29 IL_002e: stloc. 1
30. try
31 {
32 IL_002f: nop
33 IL_0030: nop
34 IL_0031: leave.s IL_0045
35 } // end .try
36 finally
37 {
38 IL_0033: ldloc. 1
39 IL_0034: ldnull
40 IL_0035: ceq
41 IL_0037: stloc.s CS$ 4 $ 0000
42 IL_0039: ldloc.s CS$ 4 $ 0000
43 IL_003b: brtrue.s IL_0044
44 IL_003d: ldloc. 1
45 IL_003e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
46 IL_0043: nop
47 IL_0044: endfinally
48 } // end handler
49
The Using statement has the same effect to release unmanaged object resources. This is often encountered in interviews, such as what are the uses of the Using keyword and similar questions. The basic ideal answer is that in addition to referencing the namespace and setting aliases for the namespace, this usage realizes the recycling of unmanaged object resources like the try finally block. Just a simple way of writing it.
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 the GC from calling the Finalize method. Because calling the Finalize method will sacrifice some performance. If your Dispose method has already cleaned up the delegated resources, there is no need for the GC to call the object's Finalize method (MSDN) again. Attached is the MSDN code for your reference.
Code
public class BaseResource: IDisposable
{
//Point to external unmanaged resources
private IntPtr handle;
// Other managed resources used by this class.
private Component Components;
// Track whether the .Dispose method is called, flag bit, control the behavior of the garbage collector
private bool disposed = false ;
//Constructor
public BaseResource()
{
// Insert appropriate constructor code here.
}
// Implement the interface IDisposable.
// Cannot be declared as a virtual method virtual.
// Subclasses cannot override this method.
public void Dispose()
{
Dispose( true );
//Leave the Finalization queue
//Set the object's blocking finalizer code
//
GC.SuppressFinalize( this );
}
// Dispose(bool disposing) is executed in 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 equal to false, the method has been called internally by the finalizer,
// You can't reference other objects, only unmanaged resources can be released.
protected virtual void Dispose( bool disposing)
{
// Check if Dispose has been called.
if ( ! this .disposed)
{
// If equal to true, release all managed and unmanaged resources
if (disposing)
{
// Release managed resources.
Components.Dispose();
}
// Release unmanaged resources, if disposing is false,
// Only the following code will be executed.
CloseHandle(handle);
handle = IntPtr.Zero;
// Note that this is not thread-safe.
// After the managed resource is released, other threads can be started to destroy the object.
// But before the disposed flag is set to true
// If thread safety is required, the client must implement it.
}
disposed = true;
}
//Use interop to call the method
// Clear unmanaged resources.
[System.Runtime.InteropServices.DllImport( " Kernel32 " )]
private extern static Boolean CloseHandle(IntPtr handle);
//Use C# destructor to implement finalizer code
// This can only be called and executed if the Dispose method has not been called.
// If you give the base class a chance to finalize.
// Don't provide a destructor for subclasses.
~BaseResource()
{
// Don't re-create cleanup code.
// Based on reliability and maintainability considerations, calling Dispose(false) is the best way
Dispose( false );
}
// Allows you to call the Dispose method multiple times,
// But an exception will be thrown if the object has been released.
// No matter when you process the object, you will check whether the object is released.
// check to see if it has been disposed.
public void DoSomething()
{
if (this .disposed)
{
throw new ObjectDisposedException();
}
}
For types where calling the Close method is more natural than the Dispose method, you can add a Close method to the base class.
The Close method takes no parameters and calls the Dispose method that performs appropriate cleanup work.
The following example demonstrates the Close method.
// Don't set the method to virtual.
// Inherited classes are not allowed to override this method
public void Close()
{
// Call Dispose parameter without parameters.
Dispose();
}
public static void Main()
{
//Insert code here to create
// and use a BaseResource object.
}