zoukankan      html  css  js  c++  java
  • .NET面试题系列(二)GC

    序言

    对象生存期

    Phone item=new Phone()

    在C#中,创建对象使用的是new关键字。 要注意的是new操作返回的并不是对象本身,而是对象的一个引用(Reference)。

    如果使用item=null;语句,将上面的item变量赋值为null,不过是切断了变量和对象之间的引用关系,对象并没有销毁,还停留在托管堆上。

    并不是将item设为null才会将对象变为无法访问的。 如果方法结束后,再没有其他地方引用对象,那么它也会变成无法访问的。

    垃圾回收机制

    什么是垃圾?

      简单理解就是没有被引用的对象。

      GC是垃圾回收(Garbage Collect)的缩写,是.NET核心机制的重要部分。她的基本工作原理就是遍历托管中的对象,标记哪些被使用对象(那些没人使用的就是所谓的垃圾),然后把可达对象转移到一个连续的地址空间(也叫压缩),其余的所有没用的对象内存被回收掉。

    托管资源:

      由CLR管理的存在于托管堆上的称为托管资源,注意这里有2个关键点,第一是由CLR管理,第二存在于托管堆上。托管资源的回收工作是不需要人工干预的,CLR会在合适的时候调用GC(垃圾回收器)进行回收。

      我们可以使用GC类提供的GC.Collect方法来使应用程序在一定程度上直接控制垃圾回收器,但是一般不要去手动干预GC。没有特殊理由,不要去调用GC.Collect(),让它自己决定什么时候去回收内存。还是人家的比较严谨。

    非托管资源:

      非托管资源是不由CLR管理,例如:Image Socket, StreamWriter, Timer, Tooltip, 文件句柄, GDI资源, 数据库连接等等资源(这里仅仅列举出几个常用的)。这些资源GC是不会自动回收的,需要手动释放。

     垃圾回收算法 - 分代(Generation)算法

      CLR托管堆支持3代:第0代,第1代,第2代。第0代的空间约为256KB,第1代约为2M,第2代约为10M。新构造的对象会被分配到第0代,

      当第0代的空间满时,垃圾回收器启动回收,不可达对象(上图C、E)会被回收,存活的对象被归为第1代。

     

      当第0代空间已满,第1代也开始有很多不可达对象以至空间将满时,这时两代垃圾都将被回收。存活下来的对象(可达对象),第0代升为第1代,第1代升为第2代。

      

    垃圾回收的基本流程包含以下三个关键步骤:

      ① 标记

      ② 清除

      ③ 压缩

    关于代龄(Generation)

      当然,实际的垃圾回收过程可能比上面的要复杂,如果没次都扫描托管堆内的所有对象实例,这样做太耗费时间而且没有必要。分代(Generation)算法是CLR垃圾回收器采用的一种机制,它唯一的目的就是提升应用程序的性能。分代回收,速度显然快于回收整个堆。分代(Generation)算法的假设前提条件:

      1、大量新创建的对象生命周期都比较短,而较老的对象生命周期会更长 

      2、对部分内存进行回收比基于全部内存的回收操作要快 

      3、新创建的对象之间关联程度通常较强。heap分配的对象是连续的,关联度较强有利于提高CPU cache的命中率

    .NET将托管堆分成3个代龄区域: Gen 0、Gen 1、Gen 2:

      第0代,新近分配在堆上的对象,从来没有被垃圾收集过。 任何一个新对象,当它第一次被分配在托管堆上时,就是第0代。

      第1代,经历过一次垃圾回收后,依然保留在堆上的对象。

      第2代,经历过两次或以上垃圾回收后,依然保留在堆上的对象。

      当进行垃圾回收时,垃圾回收器将会首先检查所有的第0代对象,并对其中可回收的对象进行清理。如果清理后获取到了足够的内存空间,经历过垃圾回收后的对象将提升为第1代对象。 

      如果所有第0代对象都检查过,但是内存空间还不够用,那么将会检查第1代对象的可访问性,并进行垃圾回收。 此时,如果经历过垃圾回收的第1代对象仍保留在堆上,则会升级为第2代对象。 类似地,如果内存仍不够用,将会对第2代对象进行检查和垃圾回收。 如果第2代的部分对象在此次回收后仍保留在堆栈上,它依然是第2代对象,因为总共只定义了三代对象。 如果第2代对象在进行完垃圾回收后空间仍然不够用,则会抛出OutOfMemoryException异常。

    大对象堆

      垃圾回收的过程中还有一个很影响性能的地方,就是在压缩的过程中,因为要批量地挪动对象,以填充腾出来的空间,如果对象很大,那么要挪动的数据量就会很大。 除此以外,如果将大对象直接分配在第0代,那么第0代的空间很快就会被占满,从而迫使CLR执行一次垃圾回收,这样执行垃圾回收的次数就会变得很频繁。因此,第二个优化策略就是采用大对象堆(LOH,Large Object Heap),当对象的大小超过指定数值(85000字节)时,就会被分配在大对象堆上。 大对象堆有几个特点:
      没有代级的概念,所有对象都被视为第2代。
      不进行对象移动和空间压缩,因为移动大对象是相对耗时的操作。 因此,需要一个链表来维护空闲区域的位置。
      对象不会被分配在末尾,而会在链表中寻找合适的位置,因此会存在碎片的问题。

    对象析构

      非托管资源回收.NET中提供释放非托管资源的方式主要是:Finalize() 和 Dispose()。

      Dispose()常用的大多是Dispose模式,主要实现方式就是实现IDisposable接口

      Finalize()  终结器(析构函数)磁盘文件、 TCP连接、 通信端口、 数据库连接等, 当对象被垃圾回收时, 只是被简单地覆盖掉, 并不会释放这些资源。

      Dispose()和Finalize()区别Finalizer的执行时间是不确定的, 有时候, 我们期望客户端在对象使用完毕后立即释放资源,此时可以实现IDisposable()接口:

    public interface IDisposable {
        void Dispose();
    }

    using

    using(A a = new A())
    {
        //使用A对象的方法
    }

      在作用域结束的时候,会自动调用A对象的Dispose方法

      但是前提A对象必须实现了IDispose接口

      否则无法使用using关键字

    性能优化建议

      尽量不要手动执行垃圾回收的方法:GC.Collect()

      垃圾回收的运行成本较高(涉及到了对象块的移动、遍历找到不再被使用的对象、很多状态变量的设置以及Finalize方法的调用等等),对性能影响也较大,因此我们在编写程序时,应该避免不必要的内存分配,也尽量减少或避免使用GC.Collect()来执行垃圾回收,一般GC会在最适合的时间进行垃圾回收。

      而且还需要注意的一点,在执行垃圾回收的时候,所有线程都是要被挂起的(如果回收的时候,代码还在执行,那对象状态就不稳定了,也没办法回收了)。

      推荐Dispose代替Finalize

      如果你了解GC内存管理以及Finalize的原理,可以同时使用Dispose和Finalize双保险,否则尽量使用Dispose。

      选择合适的垃圾回收机制:工作站模式、服务器模式

    关于GC的面试题

    1. 简述一下一个引用对象的生命周期?

    • new创建对象并分配内存
    • 对象初始化
    • 对象操作、使用
    • 资源清理(非托管资源)
    • GC垃圾回收

    2. 创建下面对象实例,需要申请多少内存空间?

    public class User
    {
        public int Age { get; set; }
        public string Name { get; set; }
    
        public string _Name = "123" + "abc";
        public List<string> _Names;
    }

    40字节内存空间,详细分析文章中给出了。

    3. 什么是垃圾?

      一个变量如果在其生存期内的某一时刻已经不再被引用,那么,这个对象就有可能成为垃圾。

    4. GC是什么,简述一下GC的工作方式?

      GC是垃圾回收(Garbage Collect)的缩写,是.NET核心机制的重要部分。她的基本工作原理就是遍历托管堆中的对象,标记哪些被使用对象(哪些没人使用的就是所谓的垃圾),然后把可达对象转移到一个连续的地址空间(也叫压缩),其余的所有没用的对象内存被回收掉。

    5. GC进行垃圾回收时的主要流程是?

      ① 标记:先假设所有对象都是垃圾,根据应用程序根Root遍历堆上的每一个引用对象,生成可达对象图,对于还在使用的对象(可达对象)进行标记(其实就是在对象同步索引块中开启一个标示位)。

      ② 清除:针对所有不可达对象进行清除操作,针对普通对象直接回收内存,而对于实现了终结器的对象(实现了析构函数的对象)需要单独回收处理。清除之后,内存就会变得不连续了,就是步骤3的工作了。

      ③ 压缩:把剩下的对象转移到一个连续的内存,因为这些对象地址变了,还需要把那些Root跟指针的地址修改为移动后的新地址。

    6. GC在哪些情况下会进行回收工作?

    • 内存不足溢出时(0代对象充满时)
    • Windwos报告内存不足时,CLR会强制执行垃圾回收
    • CLR卸载AppDomian,GC回收所有
    • 调用GC.Collect
    • 其他情况,如主机拒绝分配内存,物理内存不足,超出短期存活代的存段门限

    7. using() 语法是如何确保对象资源被释放的?如果内部出现异常依然会释放资源吗?

      using() 只是一种语法形式,其本质还是try…finally的结构,可以保证Dispose始终会被执行。

    8. 解释一下C#里的析构函数?为什么有些编程建议里不推荐使用析构函数呢?

      C#里的析构函数其实就是终结器Finalize,因为长得像C++里的析构函数而已。

      有些编程建议里不推荐使用析构函数要原因在于:第一是Finalize本身性能并不好;其次很多人搞不清楚Finalize的原理,可能会滥用,导致内存泄露,因此就干脆别用了

    9. Finalize() 和 Dispose() 之间的区别?

      Finalize() 和 Dispose()都是.NET中提供释放非托管资源的方式,他们的主要区别在于执行者和执行时间不同:

    • finalize由垃圾回收器调用;dispose由对象调用。
    • finalize无需担心因为没有调用finalize而使非托管资源得不到释放,而dispose必须手动调用。
    • finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;而dispose一调用便释放非托管资源。
    • 只有class类型才能重写finalize,而结构不能;类和结构都能实现IDispose。

      另外一个重点区别就是终结器会导致对象复活一次,也就说会被GC回收两次才最终完成回收工作,这也是有些人不建议开发人员使用终结器的主要原因。

    10. Dispose和Finalize方法在何时被调用?

    • Dispose一调用便释放非托管资源;
    • Finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;

    11. .NET中的托管堆中是否可能出现内存泄露的现象?

      是的,可能会。比如:

    • 不正确的使用静态字段,导致大量数据无法被GC释放;
    • 没有正确执行Dispose(),非托管资源没有得到释放;
    • 不正确的使用终结器Finalize(),导致无法正常释放资源;
    • 其他不正确的引用,导致大量托管对象无法被GC释放;

    12. 在托管堆上创建新对象有哪几种常见方式?

    • new一个对象;
    • 字符串赋值,如string s1=”abc”;
    • 值类型装箱;

     13.与GC相关的性能计数器

      如果遇到了性能问题,在使用debug之前分析问题较为不错的一个工具就是perfmon。

      如果您的网站遇到下面的几种情形,那还是先看看perfmon里GC相关的东西吧:

    1. cpu占用高,内存占用不高.
    2. cpu和内存占用都比较高
    3. cpu和内存占用都不高,但是网站响应很慢

    14.手工进行回收

    //对所有代进行垃圾回收。
    GC.Collect();
    //对指定的代进行垃圾回收。
    GC.Collect(int generation); 
    //强制在 System.GCCollectionMode 值所指定的时间对零代到指定代进行垃圾回收。
    GC.Collect(int generation, GCCollectionMode mode);
  • 相关阅读:
    win10 uwp 弹起键盘不隐藏界面元素
    win10 uwp 存放网络图片到本地
    win10 uwp 存放网络图片到本地
    sublime Text 正则替换
    sublime Text 正则替换
    win10 uwp 绘图 Line 控件使用
    win10 uwp 绘图 Line 控件使用
    AJAX 是什么?
    什么是 PHP SimpleXML?
    PHP XML DOM:DOM 是什么?
  • 原文地址:https://www.cnblogs.com/cnki/p/8465963.html
Copyright © 2011-2022 走看看