`

.Net内存管理和垃圾回收

阅读更多

.NET 框架的垃圾回收器管理应用程序的内存分配和释放。每次您使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行库就会继续为新对象分配空间。但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。

开发人员在内存管理方面的背景
根据您开发背景的不同,您在内存管理方面的经验也会有所不同。在某些情况下,您可能需要让您的编程习惯来适应公共语言运行库提供的自动内存管理。

COM 开发人员
COM 开发人员习惯于将实现引用计数作为一个手动的内存管理技术。每次引用一个对象,计数器就递增。如果对对象的引用超出了范围,计数器就递减。当对象的引用计数达到零时,对象被终止并释放其内存。

引用计数方案会引发许多调试错误。如果未能严格地按照引用计数的规则进行操作,对象可能被过早释放或者未引用的对象积存在内存中。循环引用也是常见的问题根源。循环引用出现在子对象引用父对象,而父对象又引用子对象时。这种情况使两个对象都不能被释放或销毁。唯一的解决方案就是让父对象和子对象都遵守一个固定的使用和销毁模式,例如总是先由父对象删除子对象。

当使用托管语言开发应用程序时,运行库的垃圾回收器免除了对引用进行计数的需要,因此也就避免了由这种手动管理内存方案引发的错误。

C++ 开发人员
C++ 开发人员熟悉与手动内存管理相关的任务。在 C++ 中,当您使用 new 运算符为对象分配内存时,您必须使用 delete 运算符释放对象的内存。这可能导致多种错误,例如忘记释放对象、引起内存泄漏或试图访问已被释放的对象的内存。

当使用 C++ 的托管扩展或其他托管语言开发应用程序时,您就不必使用 delete 运算符释放对象了。垃圾回收器在当对象不再被应用程序使用时自动为您完成这些操作。

考虑到手动管理短期对象内存的相关成本,C++ 开发人员可能习惯于避免使用这些对象。对于两次回收间创建的然后又不再使用的托管短期对象,分配和释放内存的成本非常低。在 .NET 框架中,实际上已经对垃圾回收器进行了优化来管理具有较短生存期的对象。当开发托管应用程序时,在短期对象可以简化代码的情况下使用它们是非常合适的。

Visual Basic 开发人员
Visual Basic 开发人员习惯于自动内存管理。您熟悉的编程惯例将应用于您在 .NET 框架中创建的大多数托管对象。但是,当创建或使用封装非托管资源的对象时,您应该特别注意使用 Dispose 方法的推荐设计模式。

.NET 框架支持的托管语言比此处介绍的还要多。不管您使用哪一种托管语言,.NET 框架的垃圾回收器都提供自动内存管理。它为托管对象分配和释放内存,并在必要时执行 Finalize 方法和析构函数来适当地清理非托管资源。自动内存管理通过消除手动内存管理方案引起的常见问题简化了开发。

Finalize 方法和析构函数
对于您的应用程序创建的大多数对象,可以依靠 .NET 框架的垃圾回收器隐式地执行所有必要的内存管理任务。但是,在您创建封装非托管资源的对象时,当您在应用程序中使用完这些非托管资源之后,您必须显式地释放它们。最常见的一类非托管资源就是包装操作系统资源的对象,例如文件、窗口或网络连接。虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。对于这些类型的对象,.NET 框架提供 Object.Finalize 方法,它允许对象在垃圾回收器回收该对象使用的内存适当清理其非托管资源默认情况下,Finalize 方法不执行任何操作。如果您要让垃圾回收器在回收对象的内存之前对对象执行清理操作,您必须在类中重写 Finalize 方法。当使用 C# 和 C++ 的托管扩展以外的编程语言进行开发时,您可以实现 Finalize 方法。C# 和托管扩展提供析构函数作为编写终止代码的简化机制。析构函数自动生成 Finalize 方法和对基类的 Finalize 方法的调用。在 C# 和托管扩展编程语言中,您必须为终止代码使用析构函数语法。

垃圾回收器使用名为“终止队列”的内部结构跟踪具有 Finalize 方法的对象。每次您的应用程序创建具有 Finalize 方法的对象时,垃圾回收器都在终止队列中放置一个指向该对象的项。托管堆中所有需要在垃圾回收器回收其内存之前调用它们的终止代码的对象都在终止队列中含有项。

实现 Finalize 方法或析构函数对性能可能会有负面影响,因此应避免不必要地使用它们。用 Finalize 方法回收对象使用的内存需要至少两次垃圾回收。当垃圾回收器执行回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具有终结器的不可访问对象。它改为将这些对象的项从终止队列中移除并将它们放置在标为准备终止的对象列表中。该列表中的项指向托管堆中准备被调用其终止代码的对象。一个特殊的运行库线程开始处于活动状态并调用列表中对象的 Finalize 方法,然后将这些项从列表中移除。后来的垃圾回收将确定终止的对象确实是垃圾,因为标为准备终止对象的列表中的项不再指向它们。在后来的垃圾回收中,实际上回收了对象的内存。

清理非托管资源
通过将对象的范围限制为 protected,您可以防止应用程序的用户直接调用对象的 Finalize 方法。除此之外,我们强烈建议您不要直接从应用程序代码中调用非基类的类的 Finalize 方法为适当处置非托管资源,建议您实现公共的 Dispose 或 Close 方法,这两个方法执行必要的对象清理代码。IDisposable 接口为实现接口的资源类提供 Dispose method。因为 Dispose 方法是公共的,所以应用程序的用户可以直接调用该方法来释放非托管资源占用的内存。如果正确实现了 Dispose 方法,则 Finalize 方法(或者 C# 中的析构函数或 C++ 的托管扩展)就成为避免在没有调用 Dispose 方法的情况下清理资源的一种防护措施。

实现 Dispose 方法 [C#]
类型的 Dispose 方法应该释放它拥有的所有资源。它还应该通过调用其父类型的 Dispose 方法释放其基类型拥有的所有资源。该父类型的 Dispose 方法应该释放它拥有的所有资源并同样也调用其父类型的 Dispose 方法,从而在整个基类型层次结构中传播该模式。要确保始终正确地清理资源,Dispose 方法应该可以被多次安全调用而不引发任何异常。

Dispose 方法应该为它处置的对象调用 GC.SuppressFinalize 方法。如果对象当前在终止队列中,GC.SuppressFinalize 防止其 Finalize 方法被调用。请记住,执行 Finalize 方法会大大减损性能。如果您的 Dispose 方法已经完成了清理对象的工作,那么垃圾回收器就不必调用对象的 Finalize 方法了。

下面的代码示例旨在阐释如何为封装了非托管资源的类实现 Dispose 方法的一种可能的设计模式。因为该模式是在整个 .NET 框架中实现的,所以您可能会发现它十分便于使用。但是,这不是 Dispose 方法唯一可能的实现。

资源类通常是从复杂的本机类或 API 派生的,而且必须进行相应的自定义。使用这一代码模式作为创建资源类的一个起始点,并根据封装的资源提供必要的自定义。不能编译该示例,也不能将其直接用于应用程序。

在此示例中,基类 BaseResource 实现可由类的用户调用的公共 Dispose 方法。而该方法又调用 virtual Dispose(bool disposing) 方法(Visual Basic 中的虚 Dispose(作为布尔值处置))。根据调用方的标识传递 true 或 false。以虚 Dispose 方法为对象执行适当的清理代码。

Dispose(bool disposing) 以两种截然不同的方案执行。如果处置结果为 true,则该方法已由用户的代码直接调用或间接调用,并且可处置托管资源和非托管资源。如果处置结果为 false,则该方法已由运行库从终结器内部调用,并且只能处置非托管资源。因为终结器不会以任意特定的顺序执行,所以当对象正在执行其终止代码时,不应引用其他对象。如果正在执行的终结器引用了另一个已经终止的对象,则该正在执行的终结器将失败。

基类提供的 Finalize 方法或析构函数在未能调用 Dispose 的情况下充当防护措施。Finalize 方法调用带有参数的 Dispose 方法,同时传递 false。不应在 Finalize 方法内重新创建 Dispose 清理代码。调用 Dispose(false) 可以优化代码的可读性和可维护性。

类 MyResourceWrapper 阐释如何用 Dispose 从实现资源管理的类派生。MyResourceWrapper 重写 virtual Dispose(bool disposing) 方法并为其创建的托管和非托管资源提供清理代码。MyResourceWrapper 还对其基类 BaseResource 调用 Dispose 以确保其基类能够适当地进行清理。请注意,派生类 MyResourceWrapper 没有不带参数的 Finalize 方法或 Dispose 方法,因为这两个方法从基类 BaseResource 继承它们。

 [C#] 
//  Design pattern for the base class. 
//  By implementing IDisposable, you are announcing that instances 
//  of this type allocate scarce resources.  
public   class  BaseResource: IDisposable 
 
//  Pointer to an external unmanaged resource.  
private  IntPtr handle; 
//  Other managed resource this class uses.  
private  Component Components; 
//  Track whether Dispose has been called.  
private   bool  disposed  =   false

//  Constructor for the BaseResource Object.  
public  BaseResource() 
 
//  Insert appropriate constructor code here.  
} 
 

//  Implement Idisposable. 
//  Do not make this method virtual. 
//  A derived class should not be able to override this method.  
public   void  Dispose() 
 
Dispose(
true ); 
//  Take yourself off of the Finalization queue 
//  to prevent finalization code for this object 
//  from executing a second time.  
GC.SuppressFinalize( this ); 
}
 
 

//  Dispose(bool disposing) executes in two distinct scenarios. 
//  If disposing equals true, the method has been called directly 
//  or indirectly by a user's code. Managed and unmanaged resources 
//  can be disposed. 
//  If disposing equals false, the method has been called by the 
//  runtime from inside the finalizer and you should not reference 
//  other objects. Only unmanaged resources can be disposed.  
protected   virtual   void  Dispose( bool  disposing) 
 
//  Check to see if Dispose has already been called.  
if ( ! this .disposed) 
 
//  If disposing equals true, dispose all managed 
//  and unmanaged resources.  
if (disposing) 
 
//  Dispose managed resources.  
Components.Dispose(); 
}
 
 
//  Release unmanaged resources. If disposing is false, 
//  only the following code is executed.  
CloseHandle(handle); 
handle 
=  IntPtr.Zero; 
//  Note that this is not thread safe. 
//  Another thread could start disposing the object 
//  after the managed resources are disposed, 
//  but before the disposed flag is set to true.  
} 
 
disposed 
=   true
}
 
 

//  Use C# destructor syntax for finalization code. 
//  This destructor will run only if the Dispose method 
//  does not get called. 
//  It gives your base class the opportunity to finalize. 
//  Do not provide destructors in types derived from this class.  
~ BaseResource() 
 
//  Do not re-create Dispose clean-up code here. 
//  Calling Dispose(false)is optimal in terms of 
//  readability and maintainability. 
Dispose( false ); 
}
 

//  Allow your Dispose method to be called multiple times, 
//  but throw an exception if the object has been disposed. 
//  Whenever you do something with this class, 
//  check to see if it has been disposed. 
public   void  DoSomething() 

if ( this .disposed) 

throw   new  ObjectDisposedException(); 
}
 
}
 
}
 

//  Design pattern for a derived class. 
//  Note that this derived class inherently implements the 
//  IDisposable interface because it is implemented in the base class. 
public   class  MyResourceWrapper: BaseResource 

//  A managed resource that you add in this derived class. 
private  ManagedResource addedManaged; 
//  A native unmanaged resource that you add in this derived class. 
private  NativeResource addedNative; 
private   bool  disposed  =   false

//  Constructor for this object. 
public  MyResourceWrapper() 

//  Insert appropriate constructor code here. 
}
 

protected   override   void  Dispose( bool  disposing) 

if ( ! this .disposed) 

try  

if (disposing) 

//  Release the managed resources you added in 
//  this derived class here. 
addedManaged.Dispose(); 
}
 
//  Release the native unmanaged resources you added 
//  in this derived class here. 
CloseHandle(addedNative); 
this .disposed  =   true
}
 
finally  

//  Call Dispose on your base class. 
base .Dispose(disposing); 
}
 
}
 
}
 
}
 

//  This derived class does not have a Finalize method 

 对于finalize()方法的另一个问题是开发人员不知道什么时候它将被调用。它不像C++中的析构函数在删除一个对象时被调用。为了解决这个问题,在.Net中提供了一个接口IDisposable。微软建议在实现带有fianlize()方法的类的时侯按照下面的模式定义对象:


public class Class1 : IDisposable 
{
 
public Class1()
 
{
 }


 
~Class1 ()
 
{
  
//垃圾回收器将调用该方法,因此参数需要为false。
  Dispose (false);
 }


 
//该方法定义在IDisposable接口中。
 public void Dispose ()
 
{
  
//该方法由程序调用,在调用该方法之后对象将被终结。
  
//因为我们不希望垃圾回收器再次终结对象,因此需要从终结列表中去除该对象。
  GC.SuppressFinalize (this);
  
//因为是由程序调用该方法的,因此参数为true。
  Dispose (true);
 }


 
//所有与回收相关的工作都由该方法完成
 private void Dispose(bool disposing)
   
{
  
lock(this//避免产生线程错误。
  {
   
if (disposing)
   
{
    
//需要程序员完成释放对象占用的资源。
   }


  
//对象将被垃圾回收器终结。在这里添加其它和清除对象相关的代码。
 }

}

}




现在我们了解了垃圾回收器工作的基本原理,接下来让我们看一看垃圾回收器内部是如何工作的。目前有很多种类型的垃圾回收器。微软实现了一种生存期垃圾回收器(Generational Garbage Collector)。生存期垃圾回收器将内存分为很多个托管堆,每一个托管堆对应一种生存期等级。生存期垃圾回收器遵循着下面的原则:

  新生成的对象,其生存期越短;而对象生成时间越长的对象,其生存期也就越长。对于垃圾回收器来说,回收一部分对象总是比回收全部对象要快,因此垃圾回收器对于那些生存期短的对象回收的频率要比生存期长的对象的回收频率高。

  .Net中的垃圾回收器中目前有三个生存期等级:0,1和2。0、1、2等级对应的托管堆的初始化大小分别是256K,2M和10M。垃圾回收器在发现改变大小能够提高性能的话,会改变托管堆的大小。例如当应用程序初始化了许多小的对象,并且这些对象会被很快回收的话,垃圾回收器就会将0等级的托管堆变为128K,并且提高回收的频率。如果情况相反,垃圾回收器发现在0等级的托管堆中不能回收很多空间时,就会增加托管堆的大小。

在应用程序初始化的之前,所有等级的托管堆都是空的。当对象被初始化的时候,他们会按照初始化的先后顺序被放入等级为0的托管堆中。在托管堆中对象的存放是连续的,这样使得托管堆存取对象的速度很快,因为托管对不必对内存进行搜索。垃圾回收器中保存了一个指针指向托管堆中最后一个对象之后的内存空间。图一中显示了一个包含四个对象的0等级的托管堆。


图一 包含四个对象的托管堆

当0等级托管堆被对象填满后,例如候程序初始化了新的对象,使0等级托管堆的大小超过了256K,垃圾回收器会检查托管堆中的所有对象,看是否有对象可以回收。当开始回收操作时,如前面提到的,垃圾回收器会找出根节点和根节点直接或间接引用了的对象,然后将这些对象转移到1等级托管堆中,并将0等级托管堆的指针移到最开始的位置以清除所有的对象。同时垃圾回收器会压缩1等级托管堆以保证所有对象之间没有内存空隙。当1等级托管堆满了之后,会将对象转移到2等级的托管堆。

  例如在图一之后,垃圾回收器开始回收对象,假定D对象将被回收,同时程序创建了E和F对象。这时候托管堆中的对象如图二所示。


图二 回收对象后的0等级和1等级托管堆

  然后程序创建了新的对象G和H,再一次触发了垃圾回收器。对象E将被回收。这时候托管堆中的对象如图三所示。



  生存期垃圾回收器的原则也有例外的情况。当对象的大小超过84K时,对象会被放入"大对象区"。大对象区中的对象不会被垃圾回收器回收,也不会被压缩。这样做是为了强制垃圾回收器只能回收小对象以提高程序的性能。

  控制垃圾回收器

  在.Net框架中提供了很多方法使开发人员能够直接控制垃圾回收器的行为。通过使用GC.Collect()或GC.Collect(int GenerationNumber)开发人员可以强制垃圾回收器对所有等级的托管堆进行回收操作。在大多数的情况下开发人员不需要干涉垃圾回收器的行为,但是有些情况下,例如当程序进行了非常复杂的操作后希望确认内存中的垃圾对象已经被回收,就可以使用上面的方法。另一个方法是GC.WaitForPendingFinalizers(),它可以挂起当前线程,直到处理完成器队列的线程清空该队列为止。

  使用垃圾回收器最好的方法就是跟踪程序中定义的对象,在程序不需要它们的时候手动释放它们。例如程序中的一个对象中有一个字符串属性,该属性会占用一定的内存空间。当该属性不再被使用时,开发人员可以在程序中将其设定为null,这样垃圾回收器就可以回收该字符串占用的空间。另外,如果开发人员确定不再使用某个对象时,需要同时确定没有其它对象引用该对象,否则垃圾回收器不会回收该对象。

  另外值得一提的是finalize()方法应该在较短的时间内完成,这是因为垃圾回收器给finalize()方法限定了一个时间,如果finalize()方法在规定时间内还没有完成,垃圾回收器会终止运行finalize()方法的线程。在下面这些情况下程序会调用对象的finalize()方法:

   0等级垃圾回收器已满

   程序调用了执行垃圾回收的方法

   公共语言运行库正在卸载一个应用程序域

   公共语言运行库正在被卸载

分享到:
评论

相关推荐

    深入理解.NET内存回收机制

    与传统的手动内存管理方式(如C++中的new/delete操作)不同,.NET通过引入垃圾回收机制实现了对内存的自动化管理。本文档旨在深入探讨.NET内存回收机制的工作原理及其背后的细节。 #### 二、.NET内存回收机制概述 ...

    垃圾回收:在微软.NET框架自动内存管理

    在微软.NET框架中,垃圾回收(Garbage Collection,简称GC)机制是一项非常重要的自动内存管理功能,它帮助开发者免除了手动追踪和释放内存的麻烦。这一机制自.NET框架发布以来就一直是.NET开发者必须了解的核心概念...

    c#垃圾回收资源.net回收机制

    在计算机科学中,垃圾回收(Garbage Collection)是指一种自动管理内存资源的机制,它可以释放不再使用的内存资源,以避免内存泄露和提高系统性能。在 .NET 平台中,垃圾回收机制是由 Common Language Runtime(CLR...

    【ASP.NET编程知识】.Net的GC垃圾回收原理及实现.docx

    垃圾回收(Garbage Collection)是指在 .NET Framework 中自动管理内存的机制,通过回收不再使用的对象来释放内存空间。下面对 .NET 的 GC 垃圾回收原理及实现进行详细介绍。 一、内存中的托管与非托管 在 .NET 中...

    microsoft .net il 汇编语言程序设计指南

    理解这两者对于理解.NET内存管理和垃圾回收至关重要。 5. **JIT编译**:JIT编译器将IL代码实时转化为目标平台的机器码,实现了.NET的跨平台能力。 书中还会讲解如何使用IL来实现高级功能,如方法互调(interop)、...

    .Net 垃圾回收和大对象处理 内存碎片整理.docx

    .NET框架的垃圾回收(Garbage Collection,简称GC)机制是一个自动的内存管理功能,它负责监视和清理不再使用的对象,从而释放内存资源。在.NET中,GC根据对象占用的内存大小将其分为大对象和小对象,并采取不同的...

    .NETFramework垃圾回收3.5Version.pdf

    .NET Framework的垃圾回收机制是内存管理的核心部分,主要负责托管对象的内存分配和释放。当开发者使用`new`运算符创建对象时,运行时会在托管堆中为对象分配内存。托管堆是.NET Framework管理的内存区域,其中的...

    .net内存宝典 这本书是学习.net开发的必修, 比clr via c#要强哦

    1. **垃圾回收(Garbage Collection, GC)**:.NET中的GC是自动的内存管理系统,负责管理对象的生命周期,确保程序不会因内存泄漏而崩溃。书中会详细讲解GC的工作原理,包括代际理论、内存分代、GC触发条件以及如何...

    asp.net中C#实现手动回收内存的方法

    C#语言内置了垃圾回收机制(Garbage Collection, GC),通常情况下,程序员无需直接干预内存的分配和回收。然而,在某些情况下,例如内存敏感的应用程序或者有性能要求的应用中,程序员可能需要手动触发垃圾回收来...

    收集最全的NET进阶学习网

    10. **性能优化**:深入理解.NET内存管理和垃圾回收机制,以及如何通过代码优化提升程序性能,是.NET进阶的重要环节。 11. **并发与异步编程**:掌握多线程、任务并行库(TPL)和异步编程模型,对于处理高并发场景...

    使用 .Net Memory Profiler 诊断 .NET 应用内存泄漏(方法与实践)

    .Net Memory Profiler是一款强大的分析工具,它能帮助开发者精确地识别和定位内存管理中的问题。 首先,了解内存泄漏的基本概念至关重要。内存泄漏是指程序在申请内存后,无法释放已不再使用的内存空间。在.NET框架...

    asp.net工资管理系统

    ASP.NET是微软公司推出的一种用于构建Web应用程序的框架,它基于.NET Framework,为开发者提供了强大的服务,包括语言无关性、自动垃圾回收、内存管理以及丰富的类库。在本例中,“asp.net工资管理系统”是一个使用...

    CLRProfiler 内存泄漏工具 .net

    - **垃圾收集分析**:工具可监控垃圾回收过程,展示哪些对象在多次GC后仍然存活,这往往是内存泄漏的迹象。 - **内存堆快照**:允许在不同时间点捕获内存堆的状态,通过比较快照找出内存增长的原因。 - **CPU使用...

    Pro .NET Memory Management

    .NET内存管理主要涉及两个方面:垃圾回收(Garbage Collection, GC)机制以及内存分配策略。这两个方面共同决定了应用程序运行时内存的使用情况。 ##### 2.1 垃圾回收 .NET中的垃圾回收机制是一种自动化内存管理...

    垃圾回收与资源管理

    在Java等编程语言中,垃圾回收(Garbage Collection, GC)和资源管理是至关重要的概念,它们确保程序高效、稳定地运行。本压缩包文件包含了关于垃圾回收与资源管理的源码,对于深入理解这些机制提供了宝贵的参考资料...

    .Net的对象处理和垃圾回收

    比如内存碎片整理 —— 在内存中移动大对象的成本是昂贵的,让我们研究一下垃圾回收器是如何处理大对象的,大对象对程序性能有哪些潜在的影响。  大对象堆和垃圾回收  在.Net 1.0和2.0中,如果一个对象的大小...

    .Net垃圾回收和大对象处理

    .NET垃圾回收机制是.NET框架中一个至关重要的部分,它的主要任务是自动管理应用程序的内存,以确保有效的资源使用和防止内存泄漏。在这个过程中,垃圾回收器特别关注大对象的处理,因为它们对性能和内存管理有显著...

    高清彩版 Apress.Pro.NET.Memory.Management

    在.NET框架中,内存管理主要通过托管堆(Managed Heap)和垃圾回收器(Garbage Collector, GC)来实现。托管堆是.NET运行时环境为应用程序分配的一块连续的内存区域,用于存储对象实例。当应用程序创建新对象时,...

    垃圾回收技术示例 垃圾回收技术示例

    垃圾回收(Garbage Collection, GC)是编程语言中用于自动管理内存的重要机制,尤其是在像Java、C#等语言中。这个技术示例旨在深入探讨垃圾回收的原理、工作流程以及在实际开发中的应用。 垃圾回收的基本目标是识别...

Global site tag (gtag.js) - Google Analytics