垃圾回收GC:.Net自己主动内存管理 上(三)终结器


前言


.Net下的GC全然攻克了开发人员跟踪内存使用以及控制释放内存的窘态。然而,你或午想要理解GC是怎么工作的。此系列文章中将会解释内存资源是怎么被合理分配及管理的,并包括很具体的内在算法描写叙述。同一时候,还将讨论GC的内存清理流程及什么时清理,怎么样强制清理。

终结器



GC提供了另外一个可以给你带来优点的功能:终结器。在一个资源被回收后,终结器同意一个优雅的清理操作。使用终结器,当GC释放资源所占的内存时,它们可以进行适当的自我清理。

      
简而言之:当GC检測到一个对象是垃圾时,GC会调用它的Finalize()方法(假设存在的话),而且回收对象所占内存。看以下的代码:


public class BaseObj {
public BaseObj() {
} protected override void Finalize() {
// 实现资源清理的代码
// 比方,关闭文件或网络连接
Console.WriteLine("In Finalize.");
}
}


如今你能够创建一个此对象的实例:


BaseObj bo = new BaseObj();

在将来假设GC检測到这个对象变成了垃圾,GC会去查看它是否实现了Finalize方法;假设是,就会调用此方法,最后回收此对象所占内存。

     
很多习惯C++的开发人员会迅速把析构器destructor和Finalize方法联系起来。然而,值得注意的是:它们全然不是一回事,当你试图理解终结器时你最好忘记一切有关destructor的操作。托管对象从来没有destructor周期。
当设计一个类型时,最好避免使用Finalize方法。以下列出几个原因:
  • 可被终结(Finalize)的对象会被提升到GC的更老一代中,这会增大内存压力并阻止对象内存回收即使GC觉得此对象为垃圾对象。另外,全部与此对象有直接或间接关系的对象也会被提升。GC中的代以及代的提升在兴许文章中会介绍。
  • 可被终结(Finalize)的对象须要更长时间去分配。
  • 强制GC运行终结(Finalize)方法会明显减少性能。因此,假设有10000个对象实现了Finalize方法,GC必须运行其10000次终结方法,非常伤性能。
  • 实现终结器的对象可能引用了没有终结器的对象,导致了那些没有终结器的对象的生命周期的延长。实际上,你可能会想把一个类型分成两个不同的类型:没有引用不论什么其他对象的带终结器的轻量级类型和引用了其他对象而不带终结器的类型。
  • 你无法控制终结器方法的运行时间。因此,它可能会占有一定的资源不释放直到GC的下次回收。
  • 当一个程序终止时,一些对象始终可以被訪问到而且不会运行其终结器。比方,后台线程使用的对象或者程序终止(或程序域卸载)过程中创建的对象。另外,默认地,为了程序可以终止迅速,当一个程序终止时不会调用无法被訪问到的对象的终结器。当然,全部操作系统资源都会被回收利用,可是在托管堆中的对象是不可以被恰当清理的。假设你想改变这个默认行为,你可以调用System.GC的RequestFinalizeOnShutdown方法。只是,你一定要小心地使用此方法,由于调用此方法意味着你的这个类型正在控制整个应用程序的策略。
  • 程序执行时无法保证终节器的执行顺序。比方,一个对象包括一个指针指向一个内部对象,GC检到这两个对象都是垃圾。更进一步说,内部对象的终结器先被调用。如今,外部对象的终结器可以訪问到内部对象而且调用其方法,可是内部对象已经被终结了。此时,结果是无法预知的。因为这个原因,强烈推荐终结器不要訪问不论什么内部成员对象
假设你决定让你的类型实现终结器,必须确保代码能够尽可能快的运行完成。这样能够避免可能会阻止终结器运行的动作,包含线程同步操作。还有一方面,假设你在终结器内抛出了异常,系统会觉得终结器已返回(运行完成),然后继续运行其他对象的结终器。
      
当然编译器为构造器生成代码时,编译器会自己主动地插入一个对基类构造器的调用。相同地,当一个C++编译器为析构器生成代码时,编译器会自己主动地插入一个对基类析构器的调用。然而,就像之前所说,终结器和析构器是不同的。编译器对于终结器没有特殊处理,因此编译器不会自己主动生成代码去调用基类的终结器。假设你想实现这个过程,你必须明白地在你的终结器里调用基类终结器:
public class BaseObj {
public BaseObj() {
} protected override void Finalize() {
Console.WriteLine("In Finalize.");
base.Finalize(); // 调用基类终结器
}
}
在衍生类型终结器中你会常常在最后一句代码调用基类的终结器。这样能够保持基类对象尽可能存在的更久。由于这样的调用基类终结器比較常见,C#中有一个简单的语法:

class MyObject {
~MyObject() {
//其他代码
}
}

causes the compiler to generate this code:

class MyObject {
protected override void Finalize() {
//其他代码
base.Finalize();
}
}

这和C++析构器有些像,可是记住C#不支持析构器。

终结器内部



表面上,终结器看起来直接了当:你创建一个带终结器的对象,当它被回收时,终结器被调用。实际上,有很多其它的操作你看不到。

      

当一个应用程序创建一个新的对象,new操作符在堆中给它分配内存。假设有终结器,一个指向此对象的指针会被放入终结器队列。终结器队列是一个由GC控制的内部数据结构。队列中的每一项指向一个对象,而此对象在内存回收前会调用终结器。


      下图中堆中存放着几个对象。一些对象能够被程序根訪问到,一些不能。当对象C,E,F,I和J被创建,系统检測到这些对象实现了终结器,同一时候在终结器队列里加入了指向这些对象的指针。

带有非常多对象的托管堆(对堆与栈疑惑的能够參考:深入浅出图解C#堆与栈):

Finalization Queue:终结器队列;


当GC回收内存时,对象B,E,G,H,I和J被觉得是垃圾。GC扫描终结器队列看是否存在指针指向这些对象。假设存在,在终结器队列中移除此指针并把它移动到终结器可达队列。终结器可达队列是另外一个由GC控制的内部数据结构。终结器可达队列中的每个指针代表一个已经运行过终结器的对象。

      
在GC回收内存后,托管堆变成了下图。你能够看到对象B,G和H所占的内存已经被回收利用由于它们没有终结器。然而,对象E,I和J所占的内存没有被回收由于它们的终结器还没有被运行。

GC回收之后的托管堆:

Finalization
Queue:终结器队列;Freachable Queue: 终结器可达队列

 
有一个特殊执行时线程专门用于调用终结器。通常情况下,当终结器可达队列是空队列时,这个线程进入休眠。可是,一旦终结器可达队列出现新的项,此线程苏醒,移除终结器可达队列中全部项并调用它们的终结器。因为这个机制的存在,你不可在终结器中执行不论什么基于此线程的编码。比方,不要在终节器中訪问线程本地存储器。

      
终结器队列和终结器可达队列的交互是非常有趣的。Freachable中的f代表着Finalization终结器,reachable意思是对象能够被訪问。终结器可达队列被看作类似于全局变量和静态变量一样的根。因此,GC会认定终结器可达队列中全部对象都不是垃圾。


      
总而言之,当一个带有终结器的对象不可以被訪问到,GC觉得它是垃圾。然后,GC会移动此对象在终结器队列中的指针到终结器可达队列中,这时此对象将不再是垃圾,同一时候它所占的内存也不会被回收。到此为止,GC已经完毕了一次垃圾扫描识别过程。GC压缩可释放内存,那个特殊执行时线程进行清理终结器可达队列并执行每个对象的终结器。


GC进行二次垃圾回收时,带有终结器的垃圾对象就变成真正的垃圾,由于程序根不指向它,终结器可达队列也不指向它。这时这个对象所占的内存才会被回收。这里我要指出的点就是带有终结器的对象,GC须要对它们进行两次的垃圾回收才干回收它们所占的内存。实际上,GC有时须要运行两次以上的垃圾回收由于对象可能会被提升到更老的一代中。下图显示了GC二次回收后的托管堆。

GC二次回收后的托管堆:




总结

终结器与GC的关系,我们须要掌握并理解。这有助于更深层次的理解垃圾回收GC机制。当然,还有比终结器很多其它的内容,下一节我们将介绍《复活与强制回收》。




垃圾回收GC:.Net自己主动内存管理 上(三)终结器的更多相关文章

  1. 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配

    垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  2. 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法

    垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  3. 垃圾回收算法手册:自动内存管理的艺术 BOOK

    垃圾回收算法手册:自动内存管理的艺术 2016-03-18 华章计算机 内容简介 PROSPECTUS 本书是自动内存管理领域的里程碑作品,汇集了这个领域里经过50多年的研究沉积下来的最佳实践,包含当 ...

  4. 从C#垃圾回收(GC)机制中挖掘性能优化方案

    GC,Garbage Collect,中文意思就是垃圾回收,指的是系统中的内存的分配和回收管理.其对系统性能的影响是不可小觑的.今天就来说一下关于GC优化的东西,这里并不着重说概念和理论,主要说一些实 ...

  5. GC与显式内存管理

    C++复兴的话题至今已被鼓吹两年有余,Herb Sutter和Bjarne Stroustrup等大牛们也为C++带来了大步伐的革新.然而,从这两年的效果而言,C++的复兴并没有发生.一方面随着世界经 ...

  6. Java 垃圾回收(GC) 泛读

    Java 垃圾回收(GC) 泛读 文章地址:https://segmentfault.com/a/1190000008922319 0. 序言 带着问题去看待 垃圾回收(GC) 会比较好,一般来说主要 ...

  7. 类装饰器,元类,垃圾回收GC,内建属性、内建方法,集合,functools模块,常见模块

    '''''''''类装饰器'''class Test(): def __init__(self,func): print('---初始化---') print('func name is %s'%fu ...

  8. 性能测试三十五:jvm垃圾回收-GC

    垃圾回收-GC 三个问题 哪些内存需要回收? 什么时候回收? 如何回收? YoungGC和FullGC: 新生代引发的GC叫YoungGC 老年代引发的GC叫FullGC FullGC会引起整个Jvm ...

  9. 垃圾回收gc --翻译

    原文在https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management.基本保持了平译,并在一些地方做了概念解释.(转 ...

随机推荐

  1. LeetCode Algorithm 133_Clone Graph

    Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors. OJ's ...

  2. (转)高强度密码管理软件KeePass使用详解

    转自:http://www.ruancan.com/ 算下来,你接触电脑有多久了?从第一次上网,到今天,你一共申请了多少个网站或者软件的帐号?相信这是一个几乎无人能够回答的问题. 无数人面临着这两个问 ...

  3. Java BigDecimal的基本使用方法

    1.对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类 2.运算速度比一般的+.-.*./要快 3.基本方 法描 述  ...

  4. C# 进程同步,通信

    进程之间通讯的几种方法:常用的方法有:1.使用内存映射文件2.通过共享内存DLL共享内存3.使用SendMessage向另一进程发送WM_COPYDATA消息.   发送WM_COPYDATA消息 比 ...

  5. 为什么通过空指针(NULL)能够正确调用类的部分成员函数

    #include <iostream> using namespace std; class B { public: void foo() { cout << "B ...

  6. 大战C100K之-Linux内核调优篇--转载

    原文地址:http://joyexpr.com/2013/11/22/c100k-4-kernel-tuning/ 早期的系统,系统资源包括CPU.内存等都是非常有限的,系统为了保持公平,默认要限制进 ...

  7. LA 3026 - Period KMP

    看题传送门:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show ...

  8. 10.13 android输入系统_多点触摸驱动理论与框架

    1.多点触摸驱动理论 驱动程序仅上报多个触点的位置就可以,是放大还是缩小由应用程序控制 对于多点触摸驱动在linux系统中有个输入子系统,其已经实现了open/read/write等接口 我们只需要实 ...

  9. C语言free函数的原理——————————【Badboy】

    今天在网上看到了这样一个问题,"假设malloc 了一块字符串的内存.然后,它改变了这个字符串的大小,问会不会有一部分内存没有被释放掉."这个问题,曾经的确没有细致想过. 当然.我 ...

  10. Diskpart工具应用两则:MBR/GPT分区转换 &amp; 基本/动态磁盘转换

    将基本磁盘转换为动态磁盘可直接在操作系统的磁盘管理中完毕,如图1所看到的,这一转换过程对硬盘上的数据没有影响,可是可能会影响到系统的启动(盗版系统激活会受影响). 图1:基本磁盘转换为动态磁盘 要注意 ...