Automatic Memory Management of .NET Common Language Runtime and Garbage Collector Programming(.net 内存管理及垃圾收集器编程)

Table of Contents

1. What is Common Language Runtime 1

2. Managed and Unmanaged Resource 1

3. Memory management 2

3.1 Allocating Memory 2

3.2 Releasing Memory 2

3.3 Generations and Performance 3

3.4 Releasing Memory for Unmanaged Resources 4

4 Programming for Garbage Collection 4

4.1 C# Destructor Syntax 4

4.2 Implementing a Dispose Method 4

4.3 Forcing a Garbage Collection 7

References 8


1. What is Common Language Runtime


The Common Language Runtime (CLR) is the virtual machine component of Microsoft's .NET initiative. It is Microsoft's implementation of the Common Language Infrastructure (CLI) standard, which defines an execution environment for program code. The CLR runs a form of bytecode called the Common Intermediate Language (CIL, previously known as MSIL -- Microsoft Intermediate Language).

Developers using the CLR write code in a language such as C# or VB.Net. At compile time, a .NET compiler converts such code into CIL code. At runtime, the CLR's just-in-time compiler converts the CIL code into code native to the operating system. Alternatively, the CIL code can be compiled to native code in a separate step prior to runtime. This speeds up all later runs of the software as the CIL-to-native compilation is no longer necessary.

The virtual machine aspect of the CLR allows programmers to ignore many details of the specific CPU that will execute the program. The CLR also provides other important services, including the following: Memory management , Thread management , Exception handling, Garbage collection and Security .



2. Managed and Unmanaged Resource

Managed Resource: Code executing under the control of the CLR is called managed code. For example, any code written in C# or Visual Basic .NET is managed code. Resource that can managed by CLR called Managed Resource.

Unmanaged Resource: Code that runs outside the CLR is referred to as "unmanaged code." COM components, ActiveX components, and Win32 API functions are examples of unmanaged code. And The most common type of unmanaged resource is an object that wraps an operating system resource, such as a file handle, window handle, or network connection. Although the garbage collector is able to track the lifetime of a managed object that encapsulates an unmanaged resource, it does not have specific knowledge about how to clean up the resource.

3. Memory management

3.1 Allocating Memory

When you initialize a new process, the runtime reserves a contiguous region of address space for the process. This reserved address space is called the managed heap. The managed heap maintains a pointer to the address where the next object in the heap will be allocated. Initially, this pointer is set to the managed heap's base address. All reference types are allocated on the managed heap. When an application creates the first reference type, memory is allocated for the type at the base address of the managed heap. When the application creates the next object, the garbage collector allocates memory for it in the address space immediately following the first object. As long as address space is available, the garbage collector continues to allocate space for new objects in this manner.

Allocating memory from the managed heap is faster than unmanaged memory allocation. Because the runtime allocates memory for an object by adding a value to a pointer, it is almost as fast as allocating memory from the stack. In addition, because new objects that are allocated consecutively are stored contiguously in the managed heap, an application can access the objects very quickly.

3.2 Releasing Memory

The garbage collector's optimizing engine determines the best time to perform a collection based on the allocations being made. When the garbage collector performs a collection, it releases the memory for objects that are no longer being used by the application. It determines which objects are no longer being used by examining the application's roots. Every application has a set of roots. Each root either refers to an object on the managed heap or is set to null. An application's roots include global and static object pointers, local variables and reference object parameters on a thread's stack, and CPU registers. The garbage collector has access to the list of active roots that the just-in-time (JIT) compiler and the runtime maintain. Using this list, it examines an application's roots, and in the process creates a graph that contains all the objects that are reachable from the roots.

Objects that are not in the graph are unreachable from the application's roots. The garbage collector considers unreachable objects garbage and will release the memory allocated for them. During a collection, the garbage collector examines the managed heap, looking for the blocks of address space occupied by unreachable objects. As it discovers each unreachable object, it uses a memory-copying function to compact the reachable objects in memory, freeing up the blocks of address spaces allocated to unreachable objects. Once the memory for the reachable objects has been compacted, the garbage collector makes the necessary pointer corrections so that the application's roots point to the objects in their new locations. It also positions the managed heap's pointer after the last reachable object. Note that memory is compacted only if a collection discovers a significant number of unreachable objects. If all the objects in the managed heap survive a collection, then there is no need for memory compaction.

To improve performance, the runtime allocates memory for large objects in a separate heap. The garbage collector automatically releases the memory for large objects. However, to avoid moving large objects in memory, this memory is not compacted.

3.3 Generations and Performance

To optimize the performance of the garbage collector, the managed heap is divided into three generations: 0, 1, and 2. The runtmime allocates memory on generation 0 first. After the generation 0 is full. The garbage collector begins to reclaim the memory. At the same time all programs will be suspended. After finish this. The all reachable objects will be move to generation 1. Only when generation 0 can't reclaim memory. Then the garbage collector will run for generation 1. It has the same satiation for generation 2. See following for details.

The runtime's garbage collection algorithm is based on several generalizations that the computer software industry has discovered to be true by experimenting with garbage collection schemes. First, it is faster to compact the memory for a portion of the managed heap than for the entire managed heap. Secondly, newer objects will have shorter lifetimes and older objects will have longer lifetimes. Lastly, newer objects tend to be related to each other and accessed by the application around the same time.

The runtime's garbage collector stores new objects in generation 0. Objects created early in the application's lifetime that survive collections are promoted and stored in generations 1 and 2. The process of object promotion is described later in this topic. Because it is faster to compact a portion of the managed heap than the entire heap, this scheme allows the garbage collector to release the memory in a specific generation rather than release the memory for the entire managed heap each time it performs a collection.

In reality, the garbage collector performs a collection when generation 0 is full. If an application attempts to create a new object when generation 0 is full, the garbage collector discovers that there is no address space remaining in generation 0 to allocate for the object. The garbage collector performs a collection in an attempt to free address space in generation 0 for the object. The garbage collector starts by examining the objects in generation 0 rather than all objects in the managed heap. This is the most efficient approach, because new objects tend to have short lifetimes, and it is expected that many of the objects in generation 0 will no longer be in use by the application when a collection is performed. In addition, a collection of generation 0 alone often reclaims enough memory to allow the application to continue creating new objects.

After the garbage collector performs a collection of generation 0, it compacts the memory for the reachable objects as explained in Releasing Memory earlier in this topic. The garbage collector then promotes these objects and considers this portion of the managed heap generation 1. Because objects that survive collections tend to have longer lifetimes, it makes sense to promote them to a higher generation. As a result, the garbage collector does not have to reexamine the objects in generations 1 and 2 each time it performs a collection of generation 0.

After the garbage collector performs its first collection of generation 0 and promotes the reachable objects to generation 1, it considers the remainder of the managed heap generation 0. It continues to allocate memory for new objects in generation 0 until generation 0 is full and it is necessary to perform another collection. At this point, the garbage collector's optimizing engine determines whether it is necessary to examine the objects in older generations. For example, if a collection of generation 0 does not reclaim enough memory for the application to successfully complete its attempt to create a new object, the garbage collector can perform a collection of generation 1, then generation 0. If this does not reclaim enough memory, the garbage collector can perform a collection of generations 2, 1, and 0. After each collection, the garbage collector compacts the reachable objects in generation 0 and promotes them to generation 1. Objects in generation 1 that survive collections are promoted to generation 2. Because the garbage collector supports only three generations, objects in generation 2 that survive a collection remain in generation 2 until they are determined to be unreachable in a future collection.



3.4 Releasing Memory for Unmanaged Resources

For the majority of the objects that your application creates, you can rely on the garbage collector to automatically perform the necessary memory management tasks. However, unmanaged resources require explicit cleanup. The most common type of unmanaged resource is an object that wraps an operating system resource, such as a file handle, window handle, or network connection. Although the garbage collector is able to track the lifetime of a managed object that encapsulates an unmanaged resource, it does not have specific knowledge about how to clean up the resource. When you create an object that encapsulates an unmanaged resource, it is recommended that you provide the necessary code to clean up the unmanaged resource in a public Dispose method. By providing a Dispose method, you enable users of your object to explicitly free its memory when they are finished with the object. When you use an object that encapsulates an unmanaged resource, you should be aware of Dispose and call it as necessary.



4 Programming for Garbage Collection


4.1 C# Destructor Syntax

The Object.Finalize method can be used to release the memory resource. But we cannot call or override the Object.Finalize method from the C# or the Managed Extensions for C++ programming languages. We can use destructor syntax in C#.

The following code example is written for a destructor.


 
 
  1. ~MyClass()
  2. {
  3.    // Perform some cleanup operations here.
  4. }
This code implicitly translates to the following.
  1. protected override void Finalize()
  2. {
  3.    try
  4.    {
  5.       // Perform some cleanup operations here.
  6.    }
  7.    finally
  8.    {
  9.       base.Finalize();
  10.    }
  11. }

4.2 Implementing a Dispose Method



A type's Dispose method should release all the resources that it owns. It should also release all resources owned by its base types by calling its parent type's Dispose method. A Dispose method should call the GC.SuppressFinalize method for the object it is disposing. If the object is currently on the finalization queue, GC.SuppressFinalize prevents its Finalize method from being called.

Example code:

  1. // Design pattern for the base class.
  2. // By implementing IDisposable, you are announcing that instances
  3. // of this type allocate scarce resources.
  4. public class BaseResource: IDisposable
  5. {
  6.    // Pointer to an external unmanaged resource.
  7.    private IntPtr handle;
  8.    // Other managed resource this class uses.
  9.    private Component Components;
  10.    // Track whether Dispose has been called.
  11.    private bool disposed = false;
  12.    // Constructor for the BaseResource object.
  13.    public BaseResource()
  14.    {
  15.       // Insert appropriate constructor code here.
  16.    }
  17.    // Implement IDisposable.
  18.    // Do not make this method virtual.
  19.    // A derived class should not be able to override this method.
  20.    public void Dispose()
  21.    {
  22.       Dispose(true);
  23.       // Take yourself off the Finalization queue 
  24.       // to prevent finalization code for this object
  25.       // from executing a second time.
  26.       GC.SuppressFinalize(this);
  27.    }
  28.    // Dispose(bool disposing) executes in two distinct scenarios.
  29.    // If disposing equals true, the method has been called directly
  30.    // or indirectly by a user's code. Managed and unmanaged resources
  31.    // can be disposed.
  32.    // If disposing equals false, the method has been called by the 
  33.    // runtime from inside the finalizer and you should not reference 
  34.    // other objects. Only unmanaged resources can be disposed.
  35.    protected virtual void Dispose(bool disposing)
  36.    {
  37.       // Check to see if Dispose has already been called.
  38.       if(!this.disposed)
  39.       {
  40.          // If disposing equals true, dispose all managed 
  41.          // and unmanaged resources.
  42.          if(disposing)
  43.          {
  44.             // Dispose managed resources.
  45.             Components.Dispose();
  46.          }
  47.          // Release unmanaged resources. If disposing is false, 
  48.          // only the following code is executed.
  49.          CloseHandle(handle);
  50.          handle = IntPtr.Zero;
  51.          // Note that this is not thread safe.
  52.          // Another thread could start disposing the object
  53.          // after the managed resources are disposed,
  54.          // but before the disposed flag is set to true.
  55.          // If thread safety is necessary, it must be
  56.          // implemented by the client.
  57.       }
  58.       disposed = true;         
  59.    }
  60.    // Use C# destructor syntax for finalization code.
  61.    // This destructor will run only if the Dispose method 
  62.    // does not get called.
  63.    // It gives your base class the opportunity to finalize.
  64.    // Do not provide destructors in types derived from this class.
  65.    ~BaseResource()      
  66.    {
  67.       // Do not re-create Dispose clean-up code here.
  68.       // Calling Dispose(false) is optimal in terms of
  69.       // readability and maintainability.
  70.       Dispose(false);
  71.    }
  72.    // Allow your Dispose method to be called multiple times,
  73.    // but throw an exception if the object has been disposed.
  74.    // Whenever you do something with this class, 
  75.    // check to see if it has been disposed.
  76.    public void DoSomething()
  77.    {
  78.       if(this.disposed)
  79.       {
  80.          throw new ObjectDisposedException();
  81.       }
  82.    }
  83. }
  84. // Design pattern for a derived class.
  85. // Note that this derived class inherently implements the 
  86. // IDisposable interface because it is implemented in the base class.
  87. public class MyResourceWrapper: BaseResource
  88. {
  89.    // A managed resource that you add in this derived class.
  90.    private ManagedResource addedManaged;
  91.    // A native unmanaged resource that you add in this derived class.
  92.    private NativeResource addedNative;
  93.    private bool disposed = false;
  94.   // Constructor for this object.
  95.    public MyResourceWrapper()
  96.    {
  97.       // Insert appropriate constructor code here.
  98.    }
  99.    protected override void Dispose(bool disposing)
  100.    {
  101.       if(!this.disposed)
  102.       {
  103.          try
  104.          {
  105.             if(disposing)
  106.             {
  107.                // Release the managed resources you added in
  108.                // this derived class here.
  109.                addedManaged.Dispose();         
  110.             }
  111.             // Release the native unmanaged resources you added
  112.             // in this derived class here.
  113.             CloseHandle(addedNative);
  114.             this.disposed = true;
  115.          }
  116.          finally
  117.          {
  118.             // Call Dispose on your base class.
  119.             base.Dispose(disposing);
  120.          }
  121.       }
  122.    }
  123. }
  124. // This derived class does not have a Finalize method
  125. // or a Dispose method without parameters because it inherits 
  126. // them from the base class.

4.3 Forcing a Garbage Collection

The garbage collection GC class provides the GC.Collect method, which you can use to give your application some direct control over the garbage collector. In general, you should avoid calling any of the collect methods and allow the garbage collector to run independently. You should also be careful not to place code that calls GC.Collect at a point in your program where users could call it frequently. This would defeat the optimizing engine in the garbage collector, which determines the best time to run a garbage collection.

t might be appropriate to use the GC.Collect method in a situation where there is a significant reduction in the amount of memory being used at a defined point in your application's code. For example, an application might use a document that references a significant number of unmanaged resources. When your application closes the document, you know definitively that the resources the document has been using are no longer needed. For performance reasons, it makes sense to release them all at once.


Example code:

  1. using System;
  2. namespace GCCollectExample
  3. {
  4.     class MyGCCollectClass
  5.     {
  6.         private const int maxGarbage = 1000;
  7.         static void Main()
  8.         {
  9.             // Put some objects in memory.
  10.             MyGCCollectClass.MakeSomeGarbage();
  11.             Console.WriteLine("Memory used before collection: {0}", GC.GetTotalMemory(false));
  12.          
  13.             // Collect all generations of memory.
  14.             GC.Collect();
  15.             Console.WriteLine("Memory used after full collection: {0}", GC.GetTotalMemory(true));
  16.         }
  17.         static void MakeSomeGarbage()
  18.         {
  19.             Version vt;
  20.             for(int i = 0; i < maxGarbage; i++)
  21.             {
  22.                 // Create objects and release them to fill up memory
  23.                 // with unused objects.
  24.                 vt = new Version();
  25.             }
  26.         }
  27.     }
  28. }

References

1. http://en.wikipedia.org/wiki/Common_Language_Runtime

2. http://msdn.microsoft.com/en-us/library/ms973872.aspx#manunman_mancode

3. http://msdn.microsoft.com/en-us/library/f144e03t(VS.71).aspx

4. http://msdn.microsoft.com/en-us/library/0xy59wtx(VS.71).aspx


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值