C#的GC机制

第一个就是很多人用.Net写程序,会谈到托管这个概念。那么.Net所指的资源托管到底是什么意思,是相对于所有资源,还是只限于某一方面资源?很多人对此不是很了解,其实.Net所指的托管只是针对内存这一个方面,并不是对于所有的资源;因此对于Stream,数据库的连接,GDI+的相关对象,还有Com对象等等,这些资源并不是受到.Net管理而统称为非托管资源。而对于内存的释放和回收,系统提供了GC-Garbage Collector,而至于其他资源则需要手动进行释放。

那么第二个概念就是什么是垃圾,通过我以前的文章,会了解到.Net类型分为两大类,一个就是值类型,另一个就是引用类型。前者是分配在栈上,并不需要GC回收;后者是分配在堆上,因此它的内存释放和回收需要通过GC来完成。GC的全称为“Garbage Collector”,顾名思义就是垃圾回收器,那么只有被称为垃圾的对象才能被GC回收。也就是说,一个引用类型对象所占用的内存需要被GC回收,需要先成为垃圾。那么.Net如何判定一个引用类型对象是垃圾呢,.Net的判断很简单,只要判定此对象或者其包含的子对象没有任何引用是有效的,那么系统就认为它是垃圾。

明确了这两个基本概念,接下来说说GC的运作方式以及其的功能。内存的释放和回收需要伴随着程序的运行,因此系统为GC安排了独立的线程。那么GC的工作大致是,查询内存中对象是否成为垃圾,然后对垃圾进行释放和回收。那么对于GC对于内存回收采取了一定的优先算法进行轮循回收内存资源。其次,对于内存中的垃圾分为两种,一种是需要调用对象的析构函数,另一种是不需要调用的。GC对于前者的回收需要通过两步完成,第一步是调用对象的析构函数,第二步是回收内存,但是要注意这两步不是在GC一次轮循完成,即需要两次轮循;相对于后者,则只是回收内存而已。

那么对于程序资源来说,我们应该做些什么,以及如何去做,才能使程序效率最高,同时占用资源能尽快的释放。前面也说了,资源分为两种,托管的内存资源,这是不需要我们操心的,系统已经为我们进行管理了;那么对于非托管的资源,这里再重申一下,就是Stream,数据库的连接,GDI+的相关对象,还有Com对象等等这些资源,需要我们手动去释放。

如何去释放,应该把这些操作放到哪里比较好呢。.Net提供了三种方法,也是最常见的三种,大致如下:
<!--[if !supportLists]-->1.  <!--[endif]-->析构函数;
<!--[if !supportLists]-->2.  <!--[endif]-->继承IDisposable接口,实现Dispose方法;
<!--[if !supportLists]-->3.  <!--[endif]-->提供Close方法。

经过前面的介绍,可以知道析构函数只能被GC来调用的,那么无法确定它什么时候被调用,因此用它作为资源的释放并不是很合理,因为资源释放不及时;但是为了防止资源泄漏,毕竟它会被GC调用,因此析构函数可以作为一个补救方法。而Close与Dispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被重新进行使用;而Dispose方法来说,此对象所占有的资源需要被标记为无用了,也就是此对象被销毁了,不能再被使用。例如,常见SqlConnection这个类,当调用完Close方法后,可以通过Open重新打开数据库连接,当彻底不用这个对象了就可以调用Dispose方法来标记此对象无用,等待GC回收。明白了这两种方法的意思后,大家在往自己的类中添加的接口时候,不要歪曲了这两者意思。

 

GC为了提高回收的效 率使用了Generation的概念,原理是这样的,第一次回收之前创建的对象属于Generation 0,之后,每次回收时这个Generation的号码就会向后挪一,也就是说,第二次回收时原来的Generation 0变成了Generation 1,而在第一次回收后和第二次回收前创建的对象将属于Generation 0。GC会先试着在属于Generation 0的对象中回收,因为这些是最新的,所以最有可能会被回收,比如一些函数中的局部变量在退出函数时就没有引用了(可被回收)。如果在Generation 0中回收了足够的内存,那么GC就不会再接着回收了,如果回收的还不够,那么GC就试着在Genera

在C#中,资源分为托管资源和非托管资源两种。GC在回收无用对象资源时,可以自动回收托管资源(比如托管内存),但对于非托管资源(比如Socket、文件、数据库连接)必须在程序中显式释放。

托管资源的回收首先需要GC识别无用对象,然后回收其资源。一般无用对象是指通过当前的系统根对象和调用堆栈对象不可达的对象。对象有一个重要的特点导致无用对象判断的复杂性:对象间的相互引用!如果没有相互引用,就可以通过“引用计数”这种简单高效的方式实现无用对象的判断,并实现实时回收。正是由于相互引用的存在导致GC需要设计更为复杂的算法,这样带来的最大问题在于丧失了资源回收的实时性,而变成一种不确定的方式。

对于非托管资源的释放,C#提供了两种方式:

1.Finalizer:写法貌似C++的析构函数,本质上却相差甚远。Finalizer是对象被GC回收之前调用的终结器,初衷是在这里释放非托管资源,但由于GC运行时机的不确定性,通常会导致非托管资源释放不及时。另外,Finalizer可能还会有意想不到的副作用,比如:被回收的对象已经没有被其他可用对象所引用,但Finalizer内部却把它重新变成可用,这就破坏了GC垃圾收集过程的原子性,增大了GC开销。

2.Dispose Pattern:C#提供using关键字支持Dispose Pattern进行资源释放。这样能通过确定的方式释放非托管资源,而且using结构提供了异常安全性。所以,一般建议采用Dispose Pattern,并在Finalizer中辅以检查,如果忘记显式Dispose对象则在Finalizer中释放资源。

可以说,GC为程序带来安全方便的同时也付出了不小的代价:一则丧失了托管资源回收的实时性,这在实时系统和资源受限系统中是致命的;二则没有把C#托管资源和非托管资源的管理统一起来,造成概念割裂。C++的定位之一是底层开发能力,所以不难理解GC并没有成为C++的语言特性。虽然我们在C++0x和各种第三方库都能看到GC的身影,但GC对于C++来讲并不是那么重要,至多是一个有益的补充。C++足以傲视C,并和C# GC一较高下的是它的RAII。以上介绍C#托管资源和非托管资源

 

 

 

 

首先:谈谈托管,什么叫托管,我的理解就是托付C#运行环境帮我们去管理,在这个运行环境中可以帮助我们开辟内存和释放内存,开辟内存一般用new,内存是随机分配的,释放主要靠的是GC也就是垃圾回收机制。哪么有两个大问题 

1.GC可以回收任何对象吗?

2.GC什么时候来回收对象?回收那些对象?

对于第一个问题,GC可以回收任何对象吗?我是这样理解的,首先要明白一点,C#在强大也管不到非托管代码?哪么什么是非托管代码呢?比如stream(文件),connection(数据库连接),COM(组件)等等。。哪么这些对象是需要进行连接的,比如说我们写这样一句话FileStream fs = new FileStream(“d://a.txt”,FileMode.Open);实际上已经创建了和d://a.txt的连接,如果重复两次就会报错。哪么fs这个对象叫做非托管对象,也就是说C#

不能自动去释放和d://a.txt的连接。哪么对于非托管的代码怎么办,一会我来说。

    对于第二个问题,GC什么时候来回收,回收什么对象?我想后面的就不用我说了,当然是回收托管对象了。但是GC什么时候回收?是这样的:GC是随机的,没有人知道他什么时候来,哪么我写了一个例子,证明这一点

private void button1_Click(object sender, EventArgs e)

{           

AA a = new AA();

AA b = new AA();

AA c = new AA();

AA d = new AA();

 

}

public class AA{}

在讲这个例子之前,要明白什么被称之为垃圾,垃圾就是一个内存区域,没有被任何引用指向,或者不再会被用到。哪么在第一次点击按钮的时候会生成4个对象,第二次点击按钮的时候也会生成4个对象,但是第一次生成的4个对象就已经是垃圾了,因为,第一次生成的4个对象随着button1_Click函数的结束而不会再被调用(或者说不能再被调用),哪么这个时候GC就会来回收吗?不是的!我说了GC是随机的,哪么你只管点你的,不一会GC就会来回收的(这里我们可以认为,内存中存在一定数量的垃圾之后,GC会来),要证明GC来过我们把AA类改成

public class AA

{

~AA()

{

        MessageBox.Show("析构函数被执行了");

}

}

要明白,GC清理垃圾,实际上是调用析构函数,但是这些代码是托管代码(因为里面没有涉及到Steam,Connection等。。)所以在析构函数中,我们可以只写一个MsgBox来证明刚的想法;这个时候,运行你的程序,一直点击按钮,不一会就会出现一大堆的“析构函数被执行了”…

 

    好了,然后让我们看看能不能改变GC这种为所欲为的天性,答案是可以的,我们可以通过调用GC.Collect();来强制GC进行垃圾回收,哪么button1_Click修改如下

private void button1_Click(object sender, EventArgs e)

{           

AA a = new AA();

AA b = new AA();

AA c = new AA();

AA d = new AA();

GC.Collect();

}

哪么在点击第一次按钮的时候,生成四个对象,然后强制垃圾回收,这个时候,会回收吗?当然不会,因为,这四个对象还在执行中(方法还没结束),当点第二次按钮的时候,会出现四次"析构函数被执行了",这是在释放第一次点击按钮的四个对象,然后以后每次点击都会出现四次"析构函数被执行了",哪么最后一次的对象什么时候释放的,在关闭程序的时候释放(因为关闭程序要释放所有的内存)。

 

好了,现在来谈谈非托管代码,刚才说过,非托管代码不能由垃圾回收释放,我们把AA类改成如下

public class AA

{

     FileStream fs = new FileStream("D://a.txt",FileMode.Open);

     ~AA()

     {

            MessageBox.Show("析构函数被执行了");

 }

}

private void button1_Click(object sender, EventArgs e)

{

            AA a = new AA();

}

如果是这样一种情况,哪么第二次点击的时候就会报错,原因是一个文件只能创建一个连接。哪么一定要释放掉第一个资源,才可以进行第二次的连接。哪么首先我们想到用GC.Collect(),来强制释放闲置的资源,修改代码如下:

private void button1_Click(object sender, EventArgs e)

{

            GC.Collect();

            AA a = new AA();

}

哪么可以看到,第二次点按钮的时候,确实出现了“析构函数被执行了“,但是程序仍然错了,原因前面我说过,因为Stream不是托管代码,所以C#不能帮我们回收,哪怎么办?

自己写一个Dispose方法;去释放我们的内存。代码如下:

public class AA:IDisposable

    {

        FileStream fs = new FileStream("D://a.txt",FileMode.Open);

        ~AA()

        {

            MessageBox.Show("析构函数被执行了");

 

        }

 

        #region IDisposable 成员

 

        public void Dispose()

        {

            fs.Dispose();

            MessageBox.Show("dispose执行了");

        }

 

        #endregion

    }

好了,我们看到了,继承IDisposable接口以后会有一个Dispose方法(当然了,你不想继承也可以,但是接口给我们提供一种规则,你不愿意遵守这个规则,就永远无法融入整个团队,你的代码只有你一个人能看懂),好了闲话不说,这样一来我们的button1_Click改为private void button1_Click(object sender, EventArgs e)

{

            AA a = new AA();

a.Dispose();

}

我们每次点击之后,都会发现执行了“dispose执行了”,在关闭程序的时候仍然执行了“析构函数被执行了”这意味了,GC还是工作了,哪么如果程序改为:

private void button1_Click(object sender, EventArgs e)

{

            AA a = new AA();

a.Dispose();

GC.Collect();

}

每次都既有“dispose执行了又有”“析构函数被执行了”,这意味着GC又来捣乱了,哪么像这样包含Stream connection的对象,就不用GC来清理了,只需要我们加上最后一句话GC.SuppressFinalize(this)来告诉GC,让它不用再调用对象的析构函数中。那么改写后的AA的dispose方法如下:

         public void Dispose()

        {

            fs.Dispose();

            MessageBox.Show("dispose执行了");

GC.SuppressFinalize(this);

        }

 

 

什么是GC

  GC如其名,就是垃圾收集,当然这里仅就内存而言。Garbage Collector(垃圾收集器,在不至于混淆的情况下也成为GC)以应用程序的root为基础,遍历应用程序在Heap上动态分配的所有对象[2],通过识别它们是否被引用来确定哪些对象是已经死亡的、哪些仍需要被使用。已经不再被应用程序的root或者别的对象所引用的对象就是已经死亡的对象,即所谓的垃圾,需要被回收。这就是GC工作的原理。为了实现这个原理,GC有多种算法。比较常见的算法有Reference Counting,Mark Sweep,Copy ColleC# string 是不可变的,指什么不可变ction等等。目前主流的虚拟系统.NET CLR,Java VM和Rotor都是采用的Mark Sweep算法。

C# string 是不可变的,指什么不可变

String  表示文本,即一系列 Unicode 字符。
字符串是 Unicode 字符的有序集合,用于表示文本。String 对象是 System.Char 对象的有序集合,用于表示字符串。String 对象的值是该有序集合的内容,并且该值是不可变的。

String 对象称为不可变的(只读),因为一旦创建了该对象,就不能修改该对象的值。看来似乎修改了 String 对象的方法实际上是返回一个包含修改内容的新 String 对象。

StringBuilder 类 表示可变字符字符串。无法继承此类。 
此类表示值为可变字符序列的类似字符串的对象。之所以说值是可变的,是因为在通过追加、移除、替换或插入字符而创建它后可以对它进行修改。

大多数修改此类的实例的方法都返回对同一实例的引用。由于返回的是对实例的引用,因此可以调用该引用的方法或属性。如果想要编写将连续操作依次连接起来的单个语句,这将很方便。

StringBuilder 的容量是实例在任何给定时间可存储的最大字符数,并且大于或等于实例值的字符串表示形式的长度。容量可通过 Capacity 属性或 EnsureCapacity 方法来增加或减少,但它不能小于 Length 属性的值。

如果在初始化 StringBuilder 的实例时没有指定容量或最大容量,则使用特定于实现的默认值。
  • 17
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值