当前位置: 首页 > news >正文

C# GC 垃圾回收

C# GC 垃圾回收 - 铭轩同学 - 博客园 (cnblogs.com)

C# GC 垃圾回收 - 铭轩同学 - 博客园 (cnblogs.com)icon-default.png?t=M0H8https://www.cnblogs.com/mingxuantongxue/p/3796608.html

一、托管

  .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对于内存回收采取了一定的有限算法进行轮询回收内存资源。其次,对于内存中的垃圾分为两种,一种需要调用对象的析构函数,另一种是不需要调用的。GC对于前者(需要调用析构函数的)的回收需要通过两步完成,第一步是调用对象的析构函数,第二步是回收内存,但是要注意这两步不是在GC一次轮询完成,即需要两次轮询;相对于后者(不需要调用析构函数的),则只是回收内存而已。

  对于某个具体的资源,是无法确切知道对象析构函数什么时候被调用的,以及GC什么时候会去释放和回收它所占用的内存。那么对于C、C++之类语言转换过来的程序员来说,这里需要转变观念。
对于程序资源来说,我们应该做些什么,以及如何去做,才能使程序效率最高,同时占用资源能尽快的释放。前面说过托管资源分两种,托管的内存资源,这是不需要我们操心的,系统已经为我们进行管理了,那么对于非托管资源,这里再重申一下,这就是Stream,数据库的连接,GDI+的相关对象,还有Com的相关对象,还有Com对象等等这些资源,需要我们手动去释放。
如何去释放,应该把这些操作放到哪里比较好呢。.Net提供可三种方法,也是最常见的三种,大致如下
  1、析构函数
  2、继承IDisposable接口,实现Dispose方法;
  3、提供Close方法。

  经过前面的介绍,可以知道析构函数只能被GC来调用,那么无法确定它什么时候被调用,因此用它作为资源的释放并不是很合理,因为资源释放不及时;但是为了防止资源泄露,毕竟它会被GC调用,因此析构函数可以作为一个补救方法。而Close与Dispose这两种方法的区别在于,调用完了对象的Close方法后,此对象由可能被重新进行使用;而Dispose方法来说,此对象所占用的资源需要被标记无用了,也就是此对象被销毁了,等待GC回收,不能再被使用。

  例如,常见SqlConnection这个类,当调用完Close方法后,可以通过Open重新打开数据库连接,当彻底不用这个对象了就可以调用Dispose方法来标记此对象无用,等待GC回收。明白了这两种方法的意思后,大家在往自己的类中添加的接口时候,不要歪曲了这两者意思。

接下来说说这三个函数的调用时机,我用几个试验结果来进行说明,可能会使大家的印象更深。

首先是这三种方法的实现,大致如下:

    public class DisposeClass : IDisposable
    {
        public void Close()
        {
            Debug.WriteLine("Close called!");
        }

        ~DisposeClass()
        {
            Debug.WriteLine("Destructor called!");
        }

        #region IDisposable Members

        public void Dispose()
        {
            Debug.WriteLine("Dispose called!");
        }

        #endregion
    }

  对于Close来说不属于真正意义上的释放,除了注意它需要显示被调用外,我在此对它不多说了。而对于析构函数而言,不是在对象离开作用域后立刻被执行,只有在关闭进程或者调用GC.Collect方法的时候才被调用,参看如下的代码运行结果。

namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            // Show destructor
           new Program().Create();
            Debug.WriteLine("After created!");
            new Program().CallGC();

            Console.ReadKey();
        }

        private void Create()
        {
            DisposeClass myClass = new DisposeClass();
        }

        private void CallGC()
        {
            GC.Collect();
        }
    }


    public class DisposeClass : IDisposable
    {
        public void Close()
        {
            Debug.WriteLine("Close called!");
        }

        ~DisposeClass()
        {
            Debug.WriteLine("Destructor called!");
        }

        #region IDisposable Members

        public void Dispose()
        {
            Debug.WriteLine("Dispose called!");
        }

        #endregion
    }
}

  这时在Visual Studio的输出标签里输出:

  After created!
  Destructor called!

显然在出了Create函数外,myClass对象的析构函数没有被立刻调用,而是等显示调用GC.Collect才被调用。

对于Dispose来说,也需要显示的调用,但是对于继承了IDisposable的类型对象可以使用using这个关键字,这样对象的Dispose方法在出了using范围后会被自动调用。例如:

namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            using (DisposeClass myClass = new DisposeClass())
            {
                //other operation here
            }

            Console.ReadKey();
        }
    }


    public class DisposeClass : IDisposable
    {
        public void Close()
        {
            Debug.WriteLine("Close called!");
        }

        ~DisposeClass()
        {
            Debug.WriteLine("Destructor called!");
        }

        #region IDisposable Members

        public void Dispose()
        {
            Debug.WriteLine("Dispose called!");
        }

        #endregion
    }
}

  在Visual Studio里输出:

  Dispose called!

  那么对于如上DisposeClass类型的Dispose实现来说,事实上GC还需要调用对象的析构函数,按照前面的GC流程来说,GC对于需要调用析构函数的对象来说,至少经过两个步骤,即首先调用对象的析构函数,其次回收内存。也就是说,按照上面所写的Dispose函数,虽说被执行了,但是GC还是需要执行析构函数,那么一个完整的Dispose函数,应该通过调用GC.SuppressFinalize(this )来告诉GC,让它不用再调用对象的析构函数中。那么改写后的DisposeClass如下:

    public class DisposeClass : IDisposable
    {
        public void Close()
        {
            Debug.WriteLine("Close called!");
        }

        ~DisposeClass()
        {
            Debug.WriteLine("Destructor called!");
        }

        #region IDisposable Members

        public void Dispose()
        {
            Debug.WriteLine("Dispose called!");
            //不在执行析构函数
            GC.SuppressFinalize( this );
        }

        #endregion
    }

  对以上代码进行测试:

        private void Run()
        {
            using( DisposeClass myClass = new DisposeClass() )
            {
                //other operation here
            }
        }
 
        private void CallGC()
        {
            GC.Collect();
        }
 
        // Show destructor
        Run();
        Debug.WriteLine( "After Run!" );
        CallGC();

  输出:

  Dispose called!

  After Run!

  对比表格如下:

析构函数

Dispose方法

Close方法

意义

销毁对象

销毁对象

关闭对象资源

调用方式

不能被显式调用,会被GC调用

需要显式调用

或者通过using语句

需要显式调用

调用时机

不确定

确定,在显式调用或者离开using程序块

确定,在显式调用时

 

  那么在定义一个类型的时候,是否一定要给出这三个函数地实现呢。
  我的建议大致如下。
  1.提供析构函数,避免资源未被释放,主要是指非内存资源;
  2.对于Dispose和Close方法来说,需要看所定义的类型所使用的资源(参看前面所说),而决定是否去定义这两个函数;
  3.在实现Dispose方法的时候,一定要加上“GC.SuppressFinalize( this )”语句,避免再让GC调用对象的析构函数。

2013-5-16

析构函数只能由垃圾回收器调用。

Despose()方法只能由类的使用者调用。

在C#中,凡是继承了IDisposable接口的类,都可以使用using语句,从而在超出作用域后,让系统自动调用Dispose()方法。

一个资源安全的类,都实现了IDisposable接口和析构函数。提供手动释放资源和系统自动释放资源的双保险。

相关文章:

  • Android Studio NDk调试(基于gradle-experimental插件与LLDB)
  • android studio调试c/c++代码
  • 在开发过程中使用 git rebase 还是 git merge,优缺点分别是什么?
  • Visual Studio 2019 远程调试工具(Remote Debugger)使用方法
  • Windbg使用说明书
  • 使用Windbg查看CrashDump
  • 【无标题】windbg 分析dump文件
  • Dump文件有三种:完整内存转储,内核内存转储,小内存转储。System Properties中的高级选项中可以看到这些设置。
  • Analysing crash dump in windbg
  • 【无标题】使用VS调试DUMP文件
  • 使用VS调试Dump文件
  • 【无标题】dump解析入门-用VS解析dump文件进行排障
  • Crash Dump调试:Symbol Server/Source Server、PDB原理分析
  • [笔记]Ray Tracing with Cones
  • bundletool 工具使用
  • “大数据应用场景”之隔壁老王(连载四)
  • 【347天】每日项目总结系列085(2018.01.18)
  • 2017-08-04 前端日报
  • CSS居中完全指南——构建CSS居中决策树
  • js递归,无限分级树形折叠菜单
  • RxJS: 简单入门
  • SwizzleMethod 黑魔法
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 日剧·日综资源集合(建议收藏)
  • 实现菜单下拉伸展折叠效果demo
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​渐进式Web应用PWA的未来
  • # 计算机视觉入门
  • #13 yum、编译安装与sed命令的使用
  • #git 撤消对文件的更改
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (论文阅读30/100)Convolutional Pose Machines
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (未解决)macOS matplotlib 中文是方框
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • **PHP二维数组遍历时同时赋值
  • . NET自动找可写目录
  • .NET Core 中插件式开发实现
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .net对接阿里云CSB服务
  • ;号自动换行
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [Android]一个简单使用Handler做Timer的例子
  • [AutoSar]BSW_Com02 PDU详解
  • [BZOJ1010] [HNOI2008] 玩具装箱toy (斜率优化)
  • [BZOJ4337][BJOI2015]树的同构(树的最小表示法)
  • [JavaScript]如何讓IE9, IE8, IE7, IE6關閉視窗時不彈出對話訊息
  • [LeetCode] Verify Preorder Sequence in Binary Search Tree 验证二叉搜索树的先序序列
  • [MFC] MFC消息机制的补充
  • [Pyhton]weakref 弱引用