• 托管堆和垃圾回收笔记


    托管堆基础

    在面向对象的环境中,每个类型都代表可供程序使用的一种资源。要使用这些资源,就必须为代表资源的类型分配内存。包含以下步骤:
    1. 调用IL指令newobj,为代表资源的类型分配内存(一般使用C# new操作符来完成)。
    2. 初始化内存,设置资源的初始状态并使资源可用。类型的实例构造器负责设置初始状态。
    3. 访问类型的成员来使用资源(有必要可以重复)。
    4. 摧毁资源的状态以进行清理。
    5. 释放内存。垃圾回收器独自负责这一步。

    CLR要求所有对象都从托管堆分配。

    托管堆

    进程初始化时,CLR划出一个地址空间区域作为托管堆。CLR还要维护一个指针,我们把它称作NextObjPtr。该指针指向下一个对象在堆中的分配位置。刚开始的时候,NextObjPtr设为地址空间区域的基地址。

    C#的new操作符导致CLR执行以下步骤

    1. 计算类型的字段(以及从基类型继承的字段)所需的字节数
    2. 加上对象的开销所需的字节数。

    对象开销:类型对象指针和同步块索引。32位应用程序各自需要32位共64位,即8字节。64位应用程序各自需要64位,即16字节。

    1. CLR检查区域中是否有分配对象所需的字节数。如果有足够的可用空间,就在NextObjPtr指针指向的地址处放入对象,为对象分配的字节会被清零。接着调用类型的构造器(为this参数传递NextObjPtr),new 操作符返回对象引用。就在返回这个引用之前,NextObjPtr指针的值会加上对象占用的字节数来得到一个新值,即下一个对象放入托管堆时的地址。

    垃圾回收算法

    引用计数算法(COM,Component Object Model),引用跟踪算法(CLR)。

    引用跟踪算法只关心引用类型的变量,因为只有这种变量才能引用堆上的对象;值类型变量直接包含值类型实例。引用类型变量可在许多场合使用,包括类的静态和实例字段,或者方法的参数和局部变量。我们将所有引用类型的变量都称为

    GC步骤

    1. 开始GC时,首先暂停进程中的所有线程。这样可以防止线程在CLR检查期间访问对象并更改其状态。
    2. CLR进入GC的标记阶段。CLR遍历堆中的所有对象,将同步块索引字段中的一位设为0.这表明所有对象都应该删除。
    3. 然后,CLR检查所有活动根,查看它们引用了哪些对象。这正是CLR的GC称为引用跟踪GC的原因。如果一个根包含null,CLR忽略这个根并继续检查下个根。任何根如果引用了堆上的对象,CLR都会标记那个对象,也就是将该对象的同步块索引中的位设为1.一个对象被标记后,CLR会检查那个对象中的根,标记它们引用的对象。如果发现对象已经标记,就不会重新检查对象的字段。这就避免了因为循环引用而产生死循环。

    检查完毕后,堆中的对象要么已标记,要么未标记。已标记的对象不能被垃圾回收,因为至少有一个根在引用它。我们说这种对象的可达(reachable)的,未标记的对象是不可达(unreachable)的。

    1. 进入GC的压缩(compact)阶段。压缩所有幸存下来的对象,使它们占用连续的内存空间。作为压缩阶段的一部分,CLR还要从每个根减去所引用的对象在内存中偏移的字节数。这样就能保证每个根还是引用和之前一样的对象;只是对象在内存中变换了位置。

    压缩好内存后,托管堆的NextObjPtr指针指向最后一个幸存对象之后的位置。下一个分配的对象将放到这个位置。

    重要提示

    静态字段引用的对象一直存在,知道用于加载类型的AppDomain卸载为止。内存泄漏的一个常见原因就是让静态字段引用某个集合对象,然后不停的向集合添加数据项。静态字段使集合一直存活,而集合对象使所有数据项一直存活。因此应尽量避免使用静态字段。

    垃圾回收和调试

    static void Main(string[] args)
    {
        //初始化变量t后,如果后续没有变量引用t的实例,则在强制垃圾回收时会回收t的实例。
        //Timer t = new Timer(TimerCallback, null, 0, 2000);
    
        //Timer t = null;
        //t = new Timer(TimerCallback, null, 0, 2000);//两种初始化方式一样。
    
        Timer t = new Timer(TimerCallback);
        t.Change(0, 2000);//执行初始化后,再执行调用则在发生垃圾回收的时候不会回收t的实例。
    
        //Timer t2 = t;
        Console.ReadKey();
        //t.Dispose();
    }
    
    private static void TimerCallback(object state)
    {
        Console.WriteLine(DateTime.Now);
        GC.Collect();
    }
    

    代:提升性能

    CLR的GC是基于代的垃圾回收器(generational garbage collector),它对你的代码做出了以下几点假设。

    1. 对象越新,生存期越短。
    2. 对象越老,生存期越长。
    3. 回收堆的一部分,速度快于回收整个堆。

    代的工作原理。。。。待补充。

    垃圾回收触发条件

    1. 代码显式调用System.GC的静态方法Collect方法
    2. Windows报告低内存情况
    3. CLR正在卸载AppDomain
    4. CLR正在关闭
  • 相关阅读:
    设置maven阿里云镜像和公司私服并存
    idea favorites bookmarks标签收藏夹数据丢失bug
    C#后期绑定调用COM组件
    SQL Studio 1.0:一款轻便的SQL脚本工具兼容SQL Server、MySQL、Access2007
    SQL Studio 2.0: 新版发布
    SQLHelper用的不爽,试试CmdRunner吧
    Flask + uWSGI+ Linux 指南及避坑
    Flask + Pyinstaller 打包后运行报错 SystemError
    Url重写之UrlRewriter
    .net5 提取压缩包内指定文件内容无需解压
  • 原文地址:https://www.cnblogs.com/liuguangyin/p/6500948.html
Copyright © 2020-2023  润新知