C# 垃圾回收 (GC)

上文提到引用类型的地址信息存放在栈中,实例信息存放在堆上,栈中的信息用完即自动删除,堆上的信息则不会自动删除。内存资源是有限的,堆的数据就不能无限增长,堆的数据清理则需要用到GC垃圾回收机制。接下来具体分析下GC的工作原理及应用事例。

垃圾回收分为托管资源垃圾回收和非托管资源垃圾回收。

托管资源:托管资源是由CLR全权负责的资源,简单理解由C#、VB 编写生成的就是托管资源。(C#、VB编写代码编译生成IL中间代码,IL中间代码无法直接在CPU中运行,需要基于CLR(公共语言运行时)编译成机器码运行的就是托管资源)。

非托管资源:其他语言编写,不在CLR中运行的即为非托管代码。.net framework 中,如文件读取、数据库链接读取、网络资源链接读取都为非托管代码。CLR管不到的运行代码都为非托管资源。

C#垃圾回收机制,就是CLR对堆中缓存数据的定时清理,CLR自动启动GC的时间节点暂不清楚,简单理解为CLR大管家感觉内存不够用了,就启动GC把堆中的数据校准一遍,把不再使用的给清除掉。垃圾自动回收只能回收托管资源,非托管资源不能自动释放。也是可以理解,自己的东西自己管理,对于借用别人的东西没有处决权。

在GC时为了提高效率微软对清理机制也做了相应的设计。

托管堆的工作方式非常类似于栈,对象会一个挨一个的存放,这样很容易指向下一个空闲存储单元的堆指针,提高存储效率。在GC运行时,会动堆中删除不再引用的所有对象,删除后,堆会把其他对象移动回堆的端部,再次形成一个连续的块。因此,堆可以继续像栈那样确定在什么位置存储新对象。在移动对象时,垃圾回收器会自动把新地址更新。

因为托管堆存储为连续存储,使用托管堆,就只需要读取堆指针的值即可,而不需要遍历地址的链表,来查找一个新的地方放置新数据。因此在.Net下实例一个对象会很快。访问也会很快,因为对象会压缩存储到堆上相同的内存区域,这样需要交换的页面较少。

有优点就有缺点,缺点就是在进行垃圾回收时效率较低,GC需要压缩堆,修改它移动的所有对象的引用。但微软认为,此处的效率降低相较于性能的提升还是值得的。垃圾回收器运行时,应用程序不能继续运行,进入假死状态。

为提升垃圾回收器的性能,将托管堆分为了三部分,第一部分称为第0代,第二部分称为第1代,第三部分为第2代。第0代存放最新的对象,新创建的对象都存储在第0代,垃圾回收时第0代清理压缩后移动到第1代,第1代清理压缩后会移动到第2代,第2代存储的对象一般都是经常使用的系统对象。微软认为新生成的对象大部分都是临时使用可以回收的,而且回收大量新的对象也会更快。

注意:在给新对象分配空间时,如果第0代对应的部分的容量,就会进行垃圾回收。如果新对象较大时,存放在大对象堆,使用大于8500个字节的对象时,会自动放在这个大对象堆上,而非主堆,大对象堆上的对象不执行压缩过程,由于碎片化大对象堆连续的空间不足会导致内存泄漏异常。当大对象堆做垃圾回收时,存活对象会存储到第2代上。当大对象堆

托管代码可以自动触发垃圾回收,但当频繁被动触发时效率会降低,因为GC会对整个堆做校验处理,所以需要对大数据的信息进行手动清理释放内存。非托管代码CLR无法自动垃圾回收,也需要手动对数据信息清理释放。

1、GC .Collect()函数

        private void button2_Click(object sender, EventArgs e)
        {
            Test();//第一次调用
            Test();//第二次调用
        }
        private void Test()
        {
            People p = new People();
            GC.Collect();
        }

如上段代码,实例对象什么时候会被释放,当第一次调用GC .Collect()函数时会释放刚被实例的对象吗?

答案是第二次调用GC .Collect()函数时会释放第一次生成的实例对象。因为第一次调用GC .Collect()时,第一次生成的实例对象还在使用用,实例的指针地址信息还存在栈中,手动调用时无法释放;当第二次调用GC .Collect()时会,第一次生成的实例地址信息已经从栈中释放,第一次实例的地址信息已不存在,此时第一次实例信息被清除。

由此可知GC .Collect()函数并非实时清理,使用完毕无法通过GC .Collect()立即释放。

注:GC .Collect()对所有代进行回收;GC .Collect(int) 对0到指定代进行回收;public static void Collect( int generation, GCCollectionMode mode )GCCollectionMode 值所指定的时间对0到指定代进行回收

2、析构函数、IDisposable 接口

垃圾回收器只对托管代码有效,非托管的内存如何释放?有两种机制一种是声明一个析构函数,或者实现IDisposable接口。析构函数是在释放托管资源时自动执行的函数,使用析构函数的对象需要两次才能销毁对象,第一次执行析构函数,第二次才真正删除对象,而且是不可控的,只有垃圾回收时才会触发销毁非托管信息。IDisposable接口可以替代析构函数,IDisposable接口定义了Dispose方法,可以主动调用,这个方法用来供程序员显式调用以释放非托管资源。

        ///summary
        /// 执行SQL语句,返回影响的记录数
        summary
        ///param name="SQLString"SQL语句/param
        ///returns影响的记录数/returns
        public static int ExecuteSql2(string SQLString)
        {
            SqlConnection connection = new SqlConnection(connectionString);
            SqlCommand cmd = new SqlCommand(SQLString, connection);
            try
            {
                connection.Open();
                int rows = cmd.ExecuteNonQuery();
                return rows;
            }
            catch (System.Data.SqlClient.SqlException e)
            {
                throw e;
            }
            finally
            {
                cmd.Dispose();
                connection.Dispose();
            }
        }

对于实现了Dispose接口函数的,可以用Using来自动关闭释放,但是对异常捕获不太友好。

        ///summary
        /// 执行SQL语句,返回影响的记录数
        summary
        ///param name="SQLString"SQL语句/param
        ///returns影响的记录数/returns
        public static int ExecuteSql(string SQLString)
        {
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                using (SqlCommand cmd = new SqlCommand(SQLString, connection))
                {
                    try
                    {
                        connection.Open();
                        int rows = cmd.ExecuteNonQuery();
                        return rows;
                    }
                    catch (System.Data.SqlClient.SqlException e)
                    {
                        throw e;
                    }
                }
            }
        }

一般情况下,最好是实现这两种机制,获取两种机制的有点。在一般情况下都是会调用Dispose主动释放资源,在程序猿遗忘主动释放的情况下,析构函数作为一种安全机制主动释放非托管资源。具体实现如下:

    public class ThreadDisposable : IDisposable
    {
        ~ThreadDisposable()
        {
            Dispose(false);
        }

        bool isDisposed = false;//是否已被释放
        private BackgroundWorker ReadWriteThread = new BackgroundWorker();
        public void Dispose()
        {
            Dispose(true);
            //通知垃圾回收器,此类不需要调用其析构函数了
            GC.SuppressFinalize(this);
        }
        /*思路:使用者调用 Dispose() 方法,该使用者就指定应清理所有与该对象相关的资源,包括托管和非托管的资源
         * 如果调用了析构函数,原则上所有资源仍需清理,但析构函数必须由垃圾回收器调用,而且用户不应该视图访问其他托管的对象,
         * 因为我们不能再确定它们的状态了(由GC回收不可控,有可能已经被回收)。这样情况下最好只清理非托管资源。
         */
        protected virtual void Dispose(bool disposing)
        {
            //是否已被是否
            if (isDisposed)
                return;
            if(disposing)
            {
                //cleanup managed object by calling their Dispose() methods 
                //通过调用Dispose()方法来清理托管对象
                ReadWriteThread.Dispose();
            }
            //Clearup unmanaged objects
            //释放非托管对象

            //标记为已释放
            isDisposed = true;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值