泉缘泉 发表于 2024-12-27 19:33:34

深入明白.NET内存回收机制

[媒介:].Net平台提供了许多新功能,这些功能能够帮助步伐员生产出更高效和稳定的代码。此中之一就是垃圾回收器(GC)。这篇文章将深入探讨这一功能,相识它是如何工作的以及如何编写代码来更好地利用这一.Net平台提供的功能。 
     .Net中的内存回收机制
     垃圾回收器是用来管理应用步伐的内存分配和开释的。在垃圾回收器出现从前,步伐员在利用内存时必要向体系申请内存空间。有些语言,例如Visual Basic,可以自动完成向体系申请内存空间的工作。但是在诸如Visual C++的语言中要求步伐员在步伐代码中申请内存空间。如果步伐员在利用了内存之后忘了开释内存,则会引起内存泄漏。但是有了垃圾回收器,步伐员就不必关心内存中对象在离开生存期后是否被开释的题目。当一个应用步伐在运行的时间,垃圾回收器设置了一个托管堆。托管堆和C语言中的堆向雷同,但是步伐员不必要从托管堆中开释对象,并且在托管堆中对象的存放是连续的。
     每次当开辟人员利用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。新创建的对象被放在前次创建的对象之后。垃圾回收器生存了一个指针,该指针总是指向托管堆中末了一个对象之后的内存空间。当新的对象被产生时,运行库就知道应该将新的对象放在内存的什么地方。同时开辟人员应该将雷同范例的对象放在一起。例如当开辟人员渴望向数据库写入数据的时侯,起首必要创建一个连接对象,然后是Command对象,末了是DataSet对象。如果这些对象放在托管堆相邻的区域内,存取它们就非常快。
     当垃圾回收器的指针指向托管堆以外的内存空间时,就必要回收内存中的垃圾了。在这个过程中,垃圾回收器起首假设在托管堆中全部的对象都必要被回收。然后它在托管堆中探求被根对象引用的对象(根对象就是全局,静态或处于运动中的局部变量以及寄存器指向的对象),找到后将它们加入一个有用对象的列表中,并在已经搜索过的对象中探求是否有对象被新加入的有用对象引用。直到垃圾回收器检查完全部的对象后,就有一份根对象和根对象直接或间接引用了的对象的列表,而其它没有在表中的对象就被从内存中回收。
     当对象被加入到托管堆中时,如果它实现了finalize()方法,垃圾回收器会在它的终结列表(Finalization List)中加入一个指向该对象的指针。当该对象被回收时,垃圾回收器会检查终结列表,看是否必要调用对象的finalize()方法。如果有的话,垃圾回收器将指向该对象的指针加入一个完成器队列中,该完成器队列生存了那些预备调用finalize()方法的对象。到了这一步对象还不是真正的垃圾对象。因此垃圾回收器还没有把他们从托管堆中回收。
     当对象预备被终结时,另一个垃圾回收器线程会调用在完成器队列中每个对象的finalize()方法。当调用完成后,线程将指针从完成器队列中移出,如许垃圾回收器就知道在下一次回收对象时可以扫除被终结的对象了。从上面可以看到垃圾回收机制带来的很大一部分额外工作就是调用finalize()方法,因此在实际编程中开辟人员应该避免在类中实现finalize()方法。
     对于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等级的托管堆。
   按此在新窗口打开图片
   https://i-blog.csdnimg.cn/img_convert/165fcef895895e81873faa5332d3d876.png
      图一 包含四个对象的托管堆
     当0等级托管堆被对象填满后,例如候步伐初始化了新的对象,使0等级托管堆的巨细凌驾了256K,垃圾回收器会检查托管堆中的全部对象,看是否有对象可以回收。当开始回收操作时,如前面提到的,垃圾回收器会找出根节点和根节点直接或间接引用了的对象,然后将这些对象转移到1等级托管堆中,并将0等级托管堆的指针移到最开始的位置以扫除全部的对象。同时垃圾回收器会压缩1等级托管堆以保证全部对象之间没有内存清闲。当1等级托管堆满了之后,会将对象转移到2等级的托管堆。
     例如在图一之后,垃圾回收器开始回收对象,假定D对象将被回收,同时步伐创建了E和F对象。这时间托管堆中的对象如图二所示。
   https://i-blog.csdnimg.cn/img_convert/749a9206d765d2fb836cf2a7ba5be88a.png
   按此在新窗口打开图片
图二 回收对象后的0等级和1等级托管堆
     然后步伐创建了新的对象G和H,再一次触发了垃圾回收器。对象E将被回收。这时间托管堆中的对象如图三所示。
   https://i-blog.csdnimg.cn/img_convert/4903e8c0051296490fea80310b4ef828.png
   按此在新窗口打开图片
 生存期垃圾回收器的原则也有例外的情况。当对象的巨细凌驾84K时,对象会被放入"大对象区"。大对象区中的对象不会被垃圾回收器回收,也不会被压缩。如许做是为了强制垃圾回收器只能回收小对象以提高步伐的性能。
     控制垃圾回收器
     在.Net框架中提供了很多方法使开辟人员能够直接控制垃圾回收器的行为。通过利用GC.Collect()或GC.Collect(int GenerationNumber)开辟人员可以强制垃圾回收器对全部等级的托管堆举行回收操作。在大多数的情况下开辟人员不必要干涉垃圾回收器的行为,但是有些情况下,例如当步伐举行了非常复杂的操作后渴望确认内存中的垃圾对象已经被回收,就可以利用上面的方法。另一个方法是GC.WaitForPendingFinalizers(),它可以挂起当前线程,直随处理完成器队列的线程清空该队列为止。
     利用垃圾回收器最好的方法就是跟踪步伐中界说的对象,在步伐不必要它们的时间手动开释它们。例如步伐中的一个对象中有一个字符串属性,该属性会占用一定的内存空间。当该属性不再被利用时,开辟人员可以在步伐中将其设定为null,如许垃圾回收器就可以回收该字符串占用的空间。另外,如果开辟人员确定不再利用某个对象时,必要同时确定没有其它对象引用该对象,否则垃圾回收器不会回收该对象。
     另外值得一提的是finalize()方法应该在较短的时间内完成,这是因为垃圾回收器给finalize()方法限定了一个时间,如果finalize()方法在规定时间内还没有完成,垃圾回收器会制止运行finalize()方法的线程。在下面这些情况下步伐会调用对象的finalize()方法:
      0等级垃圾回收器已满
      步伐调用了执行垃圾回收的方法
      公共语言运行库正在卸载一个应用步伐域
      公共语言运行库正在被卸载

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 深入明白.NET内存回收机制