zoukankan      html  css  js  c++  java
  • 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

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

  • 相关阅读:
    第36课 经典问题解析三
    第35课 函数对象分析
    67. Add Binary
    66. Plus One
    58. Length of Last Word
    53. Maximum Subarray
    38. Count and Say
    35. Search Insert Position
    28. Implement strStr()
    27. Remove Element
  • 原文地址:https://www.cnblogs.com/jdzhang/p/7802043.html
Copyright © 2011-2022 走看看