zoukankan      html  css  js  c++  java
  • .NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配

    在" .NET的堆和栈01,基本概念、值类型内存分配"中,了解了"堆"和"栈"的基本概念,以及值类型的内存分配。我们知道:当执行一个方法的时候,值类型实例会在"栈"上分配内存,而引用类型实例会在"堆"上分配内存,当方法执行完毕,"栈"上的实例由操作系统自动释放,"堆"上的实例由.NET Framework的GC进行回收。


    在" .NET的堆和栈02,值类型和引用类型参数传递以及内存分配"中,我们了解了值类型参数和引用类型参数在传递时的内存分配情况。


    在" .NET的堆和栈03,引用类型对象拷贝以及内存分配"中,我们了解了在拷贝引用类型对象时的内存分配情况。

    而本篇的重点要放在:对托管和非托管资源的垃圾回收、处理以及内存分配情况。

    主要包括:
    什么样的对象被GC认为是垃圾?
    GC如何回收?
        □ GC对托管堆中对象的回收
        □ GC对非托管堆中对象的回收、处理
            ※ 对资源的回收
                ○ 通过析构函数回收
                ○ 通过实现IDisposable接口回收
            ※ 对静态值类型变量的处理
            ※ 对静态引用类型变量的处理
    GC何时回收?
    GC回收之后,又执行哪些操作?       

      什么样的对象被GC认为是垃圾?

    当托管堆中的对象不被任何其它对象所引用,这些对象将成为被释放的垃圾对象等待被GC回收。

    每个应用程序都有一组根指针,这些根指针是不会被回收的,是由JIT编译器和CLR运行时维护的一个列表。主要包括:
    ● 全局/静态指针:指向全局或局部静态变量
    ● 栈指针:指向应用程序线程所需要的那部分栈上空间
    ● 寄存器指针:指向托管堆所需要的那部分CPU中的内存地址

    19


    以上,假设托管堆中有5个对象,1和5被跟指针引用,3依赖1,那么在这组托管堆对象中,2和4被回收后变成如下:

    20

    当运行时有新的引用对象产生,将会被放到托管堆中这组对象的最上面。

      GC如何回收?

      GC对托管堆中对象的回收

    GC采用一定的算法在托管堆中遍历所有对象,最终形成一个可达对象和不可达对象,不可达对象将被回收。

      GC对非托管堆中对象的回收、处理

      对资源的回收

    比如文件、数据库链接、网络链接等,这些不再托管堆中的对象如何被回收呢?


    1、通过析构函数回收

    public class Sample
    {
        //析构函数
        ~Sample()
        {
    
        }
    }

    在托管堆中,那些带有析构函数的实例,将被放置到"Finalization Queue"中。
    21

    对于那些不被任何其它对象所引用,如果没有析构函数,比如2,将被直接回收,如果有析构函数,例如4,会被放到"Freachable Queue"中,等待GC实施下一轮回收。
    22


    当为一个类添加析构函数后,为GC增加了额外的工作,代价是比较昂贵的,更现实的做法是让类来实现IDisposable接口。

    2、通过实现IDisposable接口回收

    首先让一个类实现IDisposable接口。

    public class ResourceClass : IDisposable
    {
        public void Dispose()
        {
            //TODO:实现回收逻辑
        }
    }

    在应用程序中调用如下实施回收。

    using(ResourceClass re = new ResourceClass())
    {
        
    }

      对静态值类型变量的处理

    class Counter
    {
              private static int s_Number = 0;
     
              public static int GetNextNumber()
              {
                         int newNumber = s_Number;
     
                         // DO SOME STUFF
     
                         newNumber += 1;
                         s_Number = newNumber;
                         return newNumber;
              }
    }

    如上,当方法有方法处理静态字段就需要注意了,2个线程同时调用GetNextNumber()会得到相同的结果,而我们的本意是:每调用一次方法,静态字段s_Number自增1。

    我们可以在处理逻辑块中加锁,每次只允许一个线程执行。

    class Counter
    {
              private static int s_Number = 0;
     
              public static int GetNextNumber()
              {
                        lock (typeof(Counter))
                        {
                                 int newNumber = s_Number;
     
                                 // DO SOME STUFF
     
                                 newNumber += 1;
                                 s_Number = newNumber;
                                 return newNumber;
                        }
              }
    }

      对静态引用类型变量的处理

    class Olympics
    {
              public static Collection<Runner> TryoutRunners;
    }
     
    class Runner
    {
              private string _fileName;
              private FileStream _fStream;
     
              public void GetStats()
              {
                        FileInfo fInfo = new FileInfo(_fileName);
                        _fStream = _fileName.OpenRead();
              }
    }
    以上,在GetStats()方法中,由于没有对FileStream及时关闭,如果Olympics恰巧有10万个Runner的集合,10万Runner都执行没有关闭FileStream的Gettats()方法,这将是一场灾难!

    Singleton模式可以很好地避免上述问题,它保证了在任何时候,内存中只存在某个类的一个实例。

    public class Earth
    {
        private static Earth _instance = new Earth();
        private Earth(){}
        public static Earth GetInstance(){return _instance;}
    }

    以上,单例模式的必要构成要素包括:
    1、私有静态引用类型变量
    2、私有构造函数
    3、获取类实例的静态方法

      GC何时回收?

    GC会周期性地执行垃圾回收、内存清理工作,以下情况会启动GC:
    ● 托管堆内存不足溢出时
    ● 调用GC.Collect()方法强制执行垃圾回收
    ● Windows报告内存不足
    ● CLR卸载AppDomain

      GC回收之后,又执行哪些操作?

    GC在垃圾回收之后,托管堆上将出现多个被收集对象的"空洞",为了避免托管堆的内存碎片,会重新分配内存、压缩托管堆。

    参考资料:

    C# Heap(ing) Vs Stack(ing) in .NET: Part IV

    《你必须知道的.NET(第2版)》,作者王涛。

    ".NET的堆和栈"系列包括:

    .NET的堆和栈01,基本概念、值类型内存分配

    .NET的堆和栈02,值类型和引用类型参数传递以及内存分配

    .NET的堆和栈03,引用类型对象拷贝以及内存分配

    .NET的堆和栈04,对托管和非托管资源的垃圾回收以及内存分配

  • 相关阅读:
    android 异步加载图片缩略图
    Java小工具===》在目录内查找包含××(字符串)的文件,并显示行号
    android 录像和拍照功能
    基于socket的上传下载(Java)精简版
    android 瀑布流简单例子
    创建上下文菜单及监听
    一个简单的win32截图例子
    把位图保存为文件源代码
    进程间通讯 —— 共享内存
    解决WIN32窗口不响应WM_LBUTTONDBLCLK消息
  • 原文地址:https://www.cnblogs.com/darrenji/p/3849306.html
Copyright © 2011-2022 走看看