C#内存管理和垃圾回收机制

  • 数据类型
  • 垃圾回收机制

一、数据类型

C#中的数据类型分为值类型 (Value type) 引用类型(reference type)

值  类 型: 所有的值类型都集成自 System.ValueType 上,除非加声明?否则不可为null,保存在 堆栈(Stack,先进后出上,常见的值类型有:整形、浮点型、bool、枚举等。

引用类型:所有的引用类型都继承自System.Object 上,引用类型保存在 托管堆(Head,先进先出)上,常见的类型有:数组、字符串、接口、委托、object等。

拆箱和装箱:引用类型和值类型的相互转换叫做拆装箱操作。

拆箱:拆箱就是将一个引用型对象转换成任意值型!比如:

int i=0;
System.Object obj=i;
int j=(int)obj;

 装箱:装箱就是隐式的将一个值型转换为引用型对象。比如:

int i=0;
Syste.Object obj=i;

 

二、垃圾回收机制 GC

  1、简介

      C#中和Java一样是一种系统自动回收释放资源的语言,在C#环境中通过 GC(Garbage Collect)进行系统资源回收,在数据基本类型中介绍到,C#数据类型分为引用类型和值类型,

值类型保存在Stack上,随着函数的执行作用域执行完毕而自动出栈,所以这一类型的资源不是GC所关心 对象。GC垃圾回收主要是是指保存在Heap上的资源。

       .NET的GC机制有这样两个问题:
  首先,GC并不是能释放所有的资源。它不能自动释放非托管资源。
  第二,GC并不是实时性的,这将会造成系统性能上的瓶颈和不确定性。
  GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。使用using语句可以简化资源管理。
  

  2、托管资源和非托管资源

上面介绍到,GC只释放托管资源,那么什么是托管资源和费托管资源。

  托管资源  托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源。托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收。

  非托管资源非托管资源指的是.NET不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标 等。这类资源,

垃圾回收器在清理的时候会调用Object.Finalize()方法。默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源。

总结:托管资源是释放由GC来完成,释放的时间吧不一定,一般是系统感觉内存吃紧,会进行紧急回收资源。一个对象想成为被回收,首先需要成为垃圾,GC是通过判断对象及其子对象有没有指向有效的引用,

如果没有GC就认为它是垃圾。垃圾回收机制通过一定的算法得到哪些没有被被引用、或者不再调用的资源,当这些垃圾达到一定的数量时,回启动垃圾回收机制,GC回收实际上是调用了析构函数。

垃圾回收机制意味着你不需要担心处理不再需要的对象了。咱们关心的主要是非托管资源的释放。

垃圾回收时对象一共有三代 :0,1,2。每一代都有自己的内存预算,空间不足的时候会调用垃圾回收。为了提高性能都是按代回收,第0代超预算之后就回收第0代的对象,而存活下来的对象就提升为第1代,

依次类推,而往往经过多次0代的垃圾回收才能回收一次第1代。

GC进行垃圾回收是系统决定的,下面是进行强制回收的执行代码(非特殊情况下不要使用此方法,会影响系统效率,削弱垃圾回收器中优化引擎的作用,而垃圾回收器可以确定运行垃圾回收的最佳时间

 

//对所有代进行垃圾回收。
GC.Collect();
//对指定的代进行垃圾回收。
GC.Collect(int generation); 
//强制在 System.GCCollectionMode 值所指定的时间对零代到指定代进行垃圾回收。
GC.Collect(int generation, GCCollectionMode mode); 

  3、非托管资源的释放

在定义一个类时,可以使用两种不同的机制类释放非托管资源,这两周机制有时候通常放在一起使用

1、声明析构函数(终结器)吗,作为类的成员

  构造函数可以在创建对象实例的时候执行某些操作,析构函数正好相反是资源创建以后被系统回收的时候执行的操作,垃圾回收器在回收对象之前会调用析构函数,所以在函数代码块中可以写释放非

托管资源的代码。析构函数没有返回值,没有参数,没有修饰符

 

    public class AA
        {
            ~AA()
            {
                //析构函数语法
            }
        }

 

析构函数会被编辑器翻译成下面的代码:

       protected override void Finalize()
        {
            try
            {
                // Cleanup statements...     
            }
            finally
            {
                base.Finalize();
            }
        }

最终析构函数会被翻译成上面的代码块,重写基类的Finalize()方法,然后最终调用 Base.Finalize()方法。

注意!大量的使用析构函数会影响效率!带有析构函数的对象会被系统执行两次才会被释放掉。GC执行释放资源时,没有析构函数的资源会被直接释放掉,假如目标对象有析构函数,会被先放进一个叫做“终结队列”的

项中去,然后系统调用另一个高优先级线程来执行 Finalize()方法,GC继续回收其它对象。等方法执行完以后会将对象从终结队列中清除出去,此时对象才是真正意义上的垃圾。等GC执行资源回收的时候,才回释放掉终结队列里面的对象。

总结:

  • 托管堆中内存的释放和析构函数的执行分别属于两个不同的线程。
  • 带有析构函数的对象其生命周期会变长,由上知会进行两次垃圾回收处理才能被释放,如此一来将导致程序性能的下降。
  • 若一个对象引用了其他对象时,当此对象不能够被释放时,则其引用对象也就无法进行内存的释放,也就意味着带有析构函数的对象和其引用对象将从第0代提升到第一代,毫无疑问将影响程序的性能。

综上所述,建议是不要实现其析构函数,这将大大降低程序的性能。

 

2、在类中实现 System.IDisposable 接口

 实现IDisposable接口来显示释放系统资源

class Test:IDisposable  
    {

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。
                }

                // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // TODO: 将大型字段设置为 null。

                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        ~Test()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(false);
        }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器,则取消注释以下行。
            // GC.SuppressFinalize(this); 
        }
        #endregion 
    }
View Code

① 当我们显示调用Dispose()方法以后,会执行释放费托管资源的操作,然后disposedValue会为Flase,所以我们多次调用也没有关系。Dispose()调用执行完以后,执行  GC.SuppressFinalize(this)(告诉GC不再执行终结器操作)  代码

② 如果我们不调用 Diapose()方法,系统会调用使用终结器操作,最后也是释放非托管资源。

从例子可以看出,对于手动回收(disposing为true),除了非托管资源,还可以通知其他托管对象Dispose(),因为这时候内部的托管对象肯定没回收。而到了自动回收,就不能通知其他托管对象了,因为垃圾回收可能已经把他们回收了,
而且垃圾回收会自动回收他们,也不用你通知了。

总结:

当释放非托管资源时我们应该显式的去实现Dipose()方法或者Close()方法,但是万一我们忘记显式去调用方法,此时还有一条退路,CLR会自动调用Finalize()方法,

很显然调用Finalize()方法会大大降低程序的性能,没关系,上述释放模式关键的一点是通过手动释放调用Dispose()方法可以阻止Finalize()方法的调用,换言之,上述通过手动释放既释放了非托管资源又加快了程序运行的速度,毫无疑问,这是一种完美的解决方案。

1、Finalize是系统决定执行的,我们无法干涉。Dispose是可以我们调用来释放的。

2、Finalize只能释放费托管资源,Dispose既可以释放托管资源也可以释放非托管资源。

 

posted on 2018-01-30 21:29  夜、微凉 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/wwj1992/p/8387360.html

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值