• CLR via C# 读书笔记-21.托管堆和垃圾回收


    前言

    近段时间工作需要用到了这块知识,遂加急补了一下基础,CLR中这一章节反复看了好多遍,得知一二,便记录下来,给自己做一个学习记录,也希望不对地方能够得到补充指点。

    1,.托管代码和非托管代码的区别

    2.托管堆是什么?

    3.托管堆基础,托管堆分配资源

    4.内存泄漏 内存溢出、内存损坏

    5.C# new操作符分配资源

    6.垃圾回收算法

    7.代:提升性能

    8.垃圾回收触发条件

    9.大对象

    10.使用需要特殊清理的类型

    11.使用包装了本机资源的类型

    一、托管代码和非托管代码的区别

    托管代码:执行过程交由运行时管理的代码。IL(中间语言)有时也成为托管代码。CLR负责提取托管代码,利用JIT编译为机器代码,从而执行它。(CLR提供 自动内存管理、安全边界、类型安全)

    非托管代码:在CLR外部,由操作系统直接执行的代码。(按自己的想法提供垃圾回收、类型检查、安全)例如:COM 组件、ActiveX 接口和 Win32 API 函数,数据库连接

    二、托管堆是什么?.

    托管堆:CLR要求所有对象都从托管堆中分配。进程初始化时,CLR划出一个地址空间区域作为托管堆。

    (CLR还要维护一个指针,我们称它作NextObjPtr。该指针指向下一个对象在堆中的分配位置)

    三、托管堆基础,托管堆分配资源

    在面向对象中,每个类型都代表可供程序提供的一种资源,要使用这些资源,必须为代表资源的这些类型分配内存

    1.调用IL指令newobj,为代表资源的类型分配内存(一般使用C#new操作符来完成)

    2.访问类型的成员来使用资源(有必要可以重复)

    3.摧毁资源的状态以进行清理

    4.释放内存。垃圾回收器独自负责这一步

    四、内存泄漏 内存溢出、内存损坏

    内存泄漏:申请了一块内存,但不需要时一直不去删除,导致这块内存一直被占用

    内存溢出:程序在申请内存时,没有足够的内存空间供其使用,抛出OutOfMemoryException

    内存损坏:访问被释放的内存

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

    1.计算类型的字段(以及从基类型继承的字段)所需的字节数

    2.加上对象开销所需的字节数。每个对象都有两个开销字段:类型对象指针和同步块索引。

    32位应用程序,这两个字段各自需要32位,所以每个对象要增加8字节

    64位应用程序,这两个字段各自需要64位,所以每个对象要增加16字节

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

    六、垃圾回收算法

     1.引用计数算法。

    Microsoft自己的“组件对象模型”(Component Object Model,COM),每个对象都维护着一个内存字段来统计程序中多少“部分”正在使用对象,随着每一“部分”到达代码中某个不再需要对象的地方,就递减对象的计数字段。计数对象变为0,对象就可以从内存中删除了。

    许多引用技术系统最大的问题是处理不好循环引用。这种引用会阻止两个对象的计数器达到0,所以两个对象永远不会删除

    2.引用跟踪算法

    所有引用类型的变量都成为根。

    CLR开始GC时,首先暂停进程中的所有线程,这样可以防止CLR检查期间访问对象并更改其状态。然后进入GC的标记阶段,将所有对象同步块索引的一位设为0,这表明所有对象都应被删除。任何根如果引用了堆上的对象,CLR就会将该对象的同步块索引中的位设为1,一个对象被标记后,CLR就会检查那个对象的根,标记他们的引用对象。如果发现对象已经标记,就不重新检查对象的字段。这样就避免了循环引用而产生死循环。

    已标记的对象不能被垃圾回收。CLR知道哪些对象可以幸存,哪些对象可以删除后,会进入GC的压缩阶段,压缩所有幸存下来的对象,使它们占用连续的内存空间。减少了应用程序的工作集,从而提升将来访问这些对象时的性能。解决了本机堆空间碎片化的问题。

    但是引用幸存对象的根现在引用的还是对象最初在内存中的位置,被暂停的线程恢复执行时,将访问旧的内存位置,会造成内存损坏,所以GC进入压缩阶段后,CLR还要从每个根减去所有引用的对象在内存中偏移的字节数。

    压缩好内存后,NextObjPtr将指向最后一个幸存对象之后的位置,CLR恢复应用程序的所有线程。

    注:静态字段引用的对象一直存在,直到用于加载类型的AppDomain卸载为止

    有趣的三张图示留给读者思考:

    图示一:

    图示二:

    图示三:

    七、代:提升性能

    CLR的GC是基于代的垃圾回收器,它对代码做了如下假设:

    对象越新,生存期越短。

    对象越老,生存期越长。

    回收堆的一部分,速度快于回收整个堆。

    托管堆在初始化时不包含对象。添加到堆的对象被称为第0代对象。CLR初始化时为第0代对象选择一个预存容量。如果分配一个新对象造成第0代超过预算,就必须启动一次垃圾回收。在一次垃圾回收后存活的第0代对象会成为第1代对象(事实上CLR还必须为第1代选择预算),第0代对象就不包含任何对象了。周而复始(第1代预算内存不满时不会自动释放内存),直到某一次,第0代和第1代预算内存都满了,第1代可能已经有许多对象变得不可达,这次垃圾回收器会检查第1代和第0代的所有对象,两代都被垃圾回收。和之前一样,第0代幸存者被提升为第1代,第1代幸存者被提升为第2代,第0代空出来了。

    托管堆只支持三代:第0代、第1代和第2代。CLR初始化时,会为每次带选择预算。CLR的垃圾回收器是自调节的,会根据应用程序的内存负载自动优化。

    八、垃圾回收触发条件

    1.代码显示调用System.GC的静态Collect方法

    2.Windows报告低内存情况

    CLR使用Win32函数CreateMemoryResourceNotification和QueryMemoryResourceNotification监视系统的总体内存使用情况

    3.CLR正在卸载AppDomain

    CLR认为一切都不是根,执行涵盖所有代的垃圾回收

    4.CLR正在关闭

    整个进程都要终止了,Windows将回收进程的全部内存

    九、大对象

    CLR将对象分为大对象和小对象。

    85000字节或更大的对象是大对象。

    1.大对象不是在小对象的地址空间分配,而是在进程地址空间的其它地方分配

    2.目前版本的CLR不压缩大对象,因为在内存中移动他们的代价过高

    3.大对象总是第2代,绝不可能是第0代或第1代

    十、使用需要特殊清理的类型

    有的类型除了内存还需要本机资源(文件、网络连接、套接字、互斥体),GC回收托管堆中使用的内存,会造成本机资源的泄漏。

    CLR提供了成为终结的机制,允许对象在被判定为垃圾之后,但在对象内存被回收之前执行一些代码。

    终结基类System.Object定义了受保护的虚方法Finalize。垃圾回收器判定对象是垃圾后,会调用对象的Finalize方法(如果重写)

    C#要求在类名前添加~符号来定义Finalize方法,如下图所示:

    被视为垃圾的对象在垃圾回收完毕后才调用Finalize方法,这些对象的内存不是被马上回收的。

    因为Finalize方法可能要执行访问字段的代码,可终结对象在回收时必须存活,造成被提升到另一代,使对象活的比正常时间长,增大了内存好用,所以应尽可能避免终结。

    注:Finalize方法的执行时间是控制不了的。

    十一、使用包装了本机资源的类型

    如果想允许使用者控制类所包装的本机资源的生存期,就必须实现IDisposable接口。

    如果类定义的一个字段的类型实现了dispose模式,那么类本身也实现了dispose模式。

    ”dispose一个对象”真正的意思是:清理或处置对象中包装的资源(比如它的字段引用的对象),然后等着在一次垃圾回收之后回收该对象占用的托管堆内存(此时才释放)

    using语言编译器自动生成try和finally块,在finnally块中编译器生成代码将变量转型为一个IDisposable并调用dispose方法。所以using语句只能用于实现了IDisposable接口的类型。

    一个清理资源的范例:

        class People:IDisposable
        {
            //这是一个Finalize方法
            ~People()
            {
                Dispose(false); 
            }
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
            private bool disposed = false;
            public virtual void Dispose(bool disposing)
            {
                if (!disposed)
                {
                    if (disposing)
                    { 
                        // Free any other managed objects here.
                    }
                    // Free any unmanaged objects here.
                }
                disposed = true;
            }
        }
    View Code

    参考:http://www.cnblogs.com/luminji/archive/2011/03/29/1997812.html

    天道酬勤,大道至简,坚持。

  • 相关阅读:
    BZOJ 2789: [Poi2012]Letters 树状数组 + 逆序对
    luogu 5468 [NOI2019]回家路线 最短路/暴力
    BZOJ 2427: [HAOI2010]软件安装 tarjan + 树形背包
    练手爬虫用urllib模块获取
    django安装以及配置
    web框架
    模拟百度进行图片搜索,有问题可以留言
    深入requests库params|data|json参数
    深入理解http1.x、http 2和https
    nodejs 实现 磁力链接资源搜索 BT磁力链接爬虫
  • 原文地址:https://www.cnblogs.com/jdzhang/p/7802043.html
Copyright © 2020-2023  润新知