来到个新地方,新学习C#,前面看到C#的垃圾回收,Finalize和Dispose时,总是一知半解,迷迷糊糊。这次好了,前面连续两次面试问到这个问题,脑子里不是很清晰,加上用英文来表达,更是雪上加霜的感觉。

回来,好好看了相关资料,从网上看,总没有人能说的很清晰,往往很深奥的样子,拿了本<C# language 7.0>,这样中英文结合看,总算清晰了。

现在主要是找工作,没有时间写详细,先就说个重点:

1. 垃圾回收器GC负责托管对象的回收。GC通过从应用根对象开始(如全局变量,静态变量等)去访问对象到来判断对象是否能回收,如果无法到达对象,则对像可以被回收。

而不是通过引用计数法来判断对象是否需要被回收,因为引用计数无法解决一个循环引用的两个对象的回收,他们的引用计数都为1,但实际这两个对象是整个应用程序中的孤岛对象,

从应用程序根对象开始追踪是无法访问到的,应该被回收。(后续详细解释)

2. 垃圾回收很耗性能,一般只在分配新对象发现空间不够用时才触发回收。也就是说,何时回收对用户来说是不可控,未知的。

具体回收时采用的算法是分步回收,即分代回收的方法。其基于统计原理,新产生的对象总是有最大的可能性不被使用而需要回收,比如新调用函数中的新分配的局部对象;而经过几次回收周期中存活下来的对象,则更不可能被回收掉,如全局变量等。

GC初始化默认每个对象都是需要回收的,回收触发时,通过检测从根对象开始是否能访问到来判断对象是否真的可以回收。GC将对象标签为3代,分别是0,1,和2代对象。0代是还没有经过一次回收检测的对象(没检测过,默认都是可以回收的),1代表示经过一次回收检测后存活下来(即不能回收)的对象,而2代表示经过2次以上检测仍存活的对象。

GC检测依照顺序,分别检测0代,1代和2代。但是如果检测到0代,回收部分对象后,发现空间已经足够新对象使用,回收检测立刻停止。如果仍然不够,才会继续检测1代对象,直至2代对象。通过这样的方法,避免垃圾回收机制产生作用时的性能消耗。大部分情况下,0代检测就能释放足够空间应付新对象的分配,这样后续的垃圾回收过程就可以避免。

从回收过程也可以看出,一次回收过程中,哪个对象能被回收,也是不可控,未知的。

3.垃圾回收器对堆托管对象可以回收,但对非托管对象就需要使用用户手动回收。非托管对象包括文件句柄,socket,数据库连接等。这类对象除了本身是托管外,其内部还包含对操作系统资源申请的对象,这类对象需要使用者主动释放。如文件打开后,需要关闭。

非托管对象的释放,C#提供了两种方式。一种是使用Finalize(),一种是使用Dispose().

3.1 Finalize接口是Object的虚保护函数,C#所有的对象都继承自Object,所以都天然可以重写该接口。但C#编译器在此处做了限制,外部用户只能改写析构函数,编译器编译析构函数时,自动会加上Finalize()的调用。因此加上析构函数,就等于重写了Finalize接口。析构函数是垃圾回收器负责调用的,那就意味着Finalize的调用,也是GC负责调用。考虑到上面GC回收对象的机制的特点,我们可以得出Finalize是不确定何时会调用的,虽然肯定的是,其最后始终会被调用。

Finalize被GC调用的具体过程是:新对象产生时,CLR会监测到Finalize有重写,会将对象建立一个指针引用放入一个Finalization Queue;GC发生作用时,如果该对象被检测出可以回收,则会先将其从Finalization Queue的引用队列移到另外一个叫freachable的引用队列,等待下个回收周期时,在对象空间被释放前,该队列的里对象的Finalize接口被保证调用。因此Finalize比较耗时,至少要两个回收周期才能回收。

Dispose()来自于接口IDispose,所以只要继承IDispose接口,我们就可以重写Dispose,在里面来释放非托管对象。与Finalize不同的是,Dispose接口的调用,需要使用者自己确定何时调用,因此该调用是明确见效的。使用具有Dispose接口的对象时,一般是采用catch,finally 的形式,在finally调用dispose,这样确保不管出现什么情况dispose可以被调用到。C#利用using关键字简化了该种调用方式。(using db = new DbMyContext()){ ... }这样离开这个using语句块后,CLI会自动调用using新生成对象的Dispose接口。

3.2 这两者方式各有优缺点。Finalize可以由GC确保最终会调用,非托管对象可以被释放,用户可以不用操心,但又不确定何时会释放。Dispose可以明确立刻释放,但又是不可靠的,因为使用者(程序员自己)可能会忘记显式的调用Dispose接口。

基于此,最好的办法是利用两者的优点,一起使用。就是说在这两个地方都来释放,确保非托管资源一定释放。因为Fialize的调用是垃圾回收器处理,消耗性能和时间。一旦确定已经调用了Dispose以后,后续的Finalize是可以不用调用的,GC.SuppressFinalize(this)可以完成该功能来,停止Finalize的使用。

基于此,微软给出了一种推荐的写法:

public class MyRefObj : IDispose{

private bool disposed = false; // used to identify whether the Dispose() has been called

//disposing is used to indicate whethet to explicitly call the managed objects' dispose that you expect to manage intentionally

void CleanUp(bool disposing){

  if (!this.disposed)
  {
    if (disposing){     ... // Clean up the managed resource explicitly, like call the managed objects' Dispose     }     ... // Clean up the unmanaged resource   }   disposed = true; } public void Dispose()
{
  CleanUp(true); // Now suppress finalization.
GC.SuppressFinalize(this);
} ~ MyRefObj()
{
  CleanUp(false);
}
}

说明,析构函数中的CleanUP参数不能为true,因为CleanUp里面会对一些托管资源调用Dispose接口,而托管资源何时被回收是不确定的,因此这些调用的行为是不确定的,所以不能调用。

基本要点都说完了,后面在补充细节说明。

1. C#中的Object,Class和reference的关系

Object是Class的一个实例,而Reference是一个指针。学过C/C++的很好理解,实际其内容是一个内存地址,该内存地址里存放的是实际的对象。

C#中所有的类的实例化都是通过引用实现,实际是使用new 来实例化一个类。如下举例说明:

class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** GC Basics *****");
// Create a new Car object on the managed heap. We are returned a reference to this object ("refToMyCar").
Car refToMyCar = new Car("Zippy", );
// The C# dot operator (.) is used to invoke members on the object using our reference variable.
Console.WriteLine(refToMyCar.ToString());
Console.ReadLine();
}
}

其中变量refToMyCar实际就是一个引用,一个指针,指向实际的实例对象Car("Zippy", 50)

在C#中,引用refToMyCar和实例对象Car("Zippy", 50)是存放于不同的内存块中,分别叫做堆栈区和托管内存堆区;

程序的堆栈区是一个临时数据区,存放函数调用的对象,局部变量等,函数调用完存放于堆栈区的对象的生命期就结束了,每个线程都有固定大小的堆栈。

而堆是一个全局内存区,所有分配在Managed Heap的对象,需要管理何时释放。在C/C++语言中,这部分数据需要显示的delete和new配套使用,而在C#中,会被.NET CLR来管理,就是说你在托管堆中只管生成新的实例,而不用管该实例占用空间的释放,这由CLR自动管理,实际就是垃圾回收器干的事情。

2. 垃圾回收器(GC)

GC是怎样知道一个对象不在使用了呢,这涉及到后面描述的具体算法,简而言之就是GC发现,无法从代码根应用到达的对象。可以以一个不太严谨的解释来理解,就是该分配在堆区的

对象,已经没有外部引用了。如下一段代码:

static void MakeACar()
{
// If myCar is the only reference to the Car object, it *may* be destroyed when this
method returns.
Car myCar = new Car();
}

Car的对象在函数外,没有可以引用到,就意味着该对象可以被回收了。

3.

C#有两类对象,托管和非托管对象。这个托管指的是对象被谁管理,在.net框架里,就是被CLR托管。非托管对象,指的是一些操作系统提供的资源,比如文件句柄,Socket,数据库连接等。

这些资源对象的使用,需要像操作系统申请并且使用完后,及时的归还。比如C#的FileStream类,我们使用时有如下一段代码:

FileInfo finfo = new FileInfo(FilePath);
//以打开或者写入的形式创建文件流
using (FileStream fs = finfo.OpenWrite())
...{
//根据上面创建的文件流创建写数据流
StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.Default);
//把新的内容写到创建的HTML页面中
sw.WriteLine(strhtml);
sw.Flush();
sw.Close();
}

其中,finfo, fs, sw三个分别为FileInfo,FileStream,StreamWriter的对象。

关于C#的垃圾回收机制,Finalize和Dispose的区别(自认为很清晰了,有疑问的评论)的更多相关文章

  1. java: system.gc()和垃圾回收机制finalize

    System.gc()和垃圾回收机制前的收尾方法:finalize(收尾机制) 程序退出时,为每个对象调用一次finalize方法,垃圾回收前的收尾方法 System.gc() 垃圾回收方法 clas ...

  2. Android_对android虚拟机的理解,包括内存管理机制垃圾回收机制。dalvik和art区别

    虚拟机很小,空间很小,谈谈移动设备的虚拟机的大小限制 16M ,谈谈加载图片的时候怎么处理大图片的,outmemoryExceptionBitmapFactory.option 垃圾回收,没有引用的对 ...

  3. .net垃圾回收机制编程调试试验

    1. 什么是CLR GC? 它是一个基于引用跟踪和代的垃圾回收器. 从本质上,它为系统中所有活跃对象都实现了一种引用跟踪模式,如果一个对象没有任何引用指向它,那么这个对象就被认为是垃圾对象,并且可以被 ...

  4. 垃圾回收机制GC知识再总结兼谈如何用好GC

    一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对资源(内存使用)管理的方式,常见的一般 ...

  5. JAVA的垃圾回收机制

    1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的 ...

  6. JVM的生命周期、体系结构、内存管理和垃圾回收机制

    一.JVM的生命周期 JVM实例:一个独立运行的java程序,是进程级别 JVM执行引擎:用户运行程序的线程,是JVM实例的一部分 JVM实例的诞生 当启动一个java程序时.一个JVM实例就诞生了, ...

  7. java JVM垃圾回收机制

    Java语言出来之前,大家都在拼命的写C或者C++的程序,而此时存在一个很大的矛盾,C++等语言创建对象要不断的去开辟空间,不用的时候有需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都 ...

  8. 【转】【C#】C# 垃圾回收机制

    摘要:今天我们漫谈C#中的垃圾回收机制,本文将从垃圾回收机制的原理讲起,希望对大家有所帮助. GC的前世与今生 虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久.早在1958年,由 ...

  9. Java高级之虚拟机垃圾回收机制

    博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 区别于C语言手动回收,Java自动执行垃圾回收,但为了执行高效 ...

随机推荐

  1. Dynamic Rankings(树状数组套权值线段树)

    Dynamic Rankings(树状数组套权值线段树) 给定一个含有n个数的序列a[1],a[2],a[3]--a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[ ...

  2. springboot2.0+Neo4j+d3.js构建知识图谱

    Welcome to the Neo4j wiki! 初衷这是一个知识图谱构建工具,最开始是对产品和领导为了做ppt临时要求配合做图谱展示的不厌其烦,做着做着就抽出一个目前看着还算通用的小工具 技术栈 ...

  3. 洛谷P2068 统计和

    题目描述 给定一个长度为\(n(n \leq 100000)\),初始值都为\(0\)的序列,\(x(x \leq 10000)\)次的修改某些位置上的数字,每次加上一个数,然后提出\(y (y \l ...

  4. jmeter将参数值写入到指定文件(转)

    有时在测试过程中需要将测试过程中生成的参数保存下来,jmeter并没有此类功能,此时,可以 通过beanshell编写代码来实现 思路: 每次请求响应返回后,通过正则表达式获取到需要保存的值,通过Be ...

  5. event对象的使用注意事项

    首先event是在事件发生的时候产生的,所以必须在事件发生的事件监听函数里面使用他.不然的话就没用的.会找不到这个事件: 错误的写法: 报错的内容: 正确的写法:

  6. noip2018复习计划啊

    需要复习的算法额: exgcd CRT INV dij spfa(~) 矩阵快速幂~高斯消元 tarjan(scc,bcc) treap splay 线段树 dp(决策单调,斜率,四边形不等式) rh ...

  7. Helvetic Coding Contest 2016 online mirror B1

    Description The zombies are gathering in their secret lair! Heidi will strike hard to destroy them o ...

  8. 实现一个类似bootstrap的多级下拉菜单

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. HDU 5908 Abelian Period 可以直接用multiset

    http://acm.hdu.edu.cn/showproblem.php?pid=5908 要求把数组分成k组使得每组中的元素出现次数相同 就是分成k个集合,那么直接用multiset判定就可以 有 ...

  10. (转)CentOS系统启动流程图文详解

    CentOS系统启动流程图文详解. 原文:http://www.linuxidc.com/Linux/2017-03/141966.htm 熟悉系统启动流程对于我们学习Linux系统是非常有帮助的,虽 ...