.Net中的垃圾回收

  • 当某线程触发GC时,任何线程不能再访问任何的对象.
    • 在GC执行时,可能会修改对象的地址.
    • GC执行时,大多使用劫持法,将所有的线程挂起.
    • 对于含有Loop,且Loop内不再调用其它方法时.会生成一个编译时表.当线程的指针指令执行到该处时,会认为到达了一个安全点,线程可以被挂起.
    • 对于其它方法,GC会劫持线程.修改线程堆栈,使其正在执行的方法返回时指向一个CLR内一个特例函数,使得在方法返回时挂起.
    • 当方法慢时,CLR会等待250ms,然后再次检测线程是否为安全点/可被劫持.该过程一直持续到所有线程可被挂起.
    • 只有托管代码可被挂起.而非托管代码已经被固化,会继续执行,然后执行到托管代码时,立即被挂起.
  • 多处理器的优化.
    • 1)托管堆的0代对象被分配到N个内存区内.
    • 2)宿主APP可加载CLR,并使CLR调整GC方式.<runtime><GcServerenabled= true>使托管堆按CPU数划分为N区(即N个托管堆),各区并行执行自己的GC.使用GCSetting.ISServerGC判断.
    • 3)并发收集:<GCConcurrentEnabled =true>在APP运行时,有一普通优先级的后台线程来标记不可达对象,其与APP竞争CPU,当标记完成后即可进行GC.这会造成APP工作集增加,但多CPU时有优势.
    • 4)大尺寸对象:占内存>85KB.被分配到特殊的大尺寸对象堆上,为2代对象,永远不被压缩,但由于其Size未来可变,所以不可假设其地址恒定.应该仅为长期保存的对象保存为大尺寸对象.
  • 代.
    • 基于代的GC的假设:对象越老/,生存期越长/.
    • 对堆的部分GC在性能上远大于对整个堆的GC.
    • 堆初始化时不含有对象,0代对象新构建的对象.CLR初始化时会給0/1/2代堆一个预算容量:256K/2M/10M.
    • 0代超量时,第一次GC,之后剩下的对象升格为第一代对象(由第0代对象GC后的残留者),但是如果0代为空,之后再创建的对象为第0.之后,0代超量而1代未超,只对0GC,节省性能.1代超量后对其GC,之后的剩余者称为2.GC会自学习APP行为来自动调整各代容量.
  • CLR的内存都是从堆上分配,APP初始化后,CLR留一块连续的地址,保留一指针.
    • NewObject:计算对象Size;+两个开销(2*32B);分配.对托管堆分配对象仅为在指针上添加一个Value.在未有充足空间时GC.exe.
  • App有一组根(一个存储位置指针,Static字段(类内),引用变量,方法参数,CPU寄存器).
    • 过程:标记(Ref一对象,则将对象的同步索引块设一位标记).
    • 压缩(搬移对象以使其内存连续,这样使得引用的根无效,所以重新访问APP的根,修改其指针).
    • 要求:GC能够识别出APP,还要找到所有对象指针.C++允许指针转换所以不支持GC,CLR通过元数据Info总能知道对象的实际类型.
  • 本地资源.
    • 如果用到本地资源,应使用终结以允许GC之前执行一些清理(否则泄露).
    • 1)抽象类CriticalFinalizerObject:首次构造该类型对象时,CLR立即对继承层次中所有Finalize()进行编译以确保终结(当内存小时无法编译之,当方法内引用其他AM中的类型时,可能无法释放本地资源).
    • 2)在调用非Finalize()Finalize,再调用Finalize()的终结.
    • 3)APP域被宿主APP粗鲁中断时自动Finalize().
  • 终止亦可用于托管资源的GC.
    • 但性能:1)由于终结对象的指针被放于终结链上,其分配耗时.2)终结对象会被提升代级别,增大内存压力.3)导致APP变慢(额外的处理开销).
    • 而且Finalize()的执行无法控制且调用顺序不定,调用一个对象的Finalize()时,此对象有可能已被终结,此时出错.  (调用Finalize()的时机:0代对象充满;GC.Collect,Win内存不足,ClR卸载APP域,CLR被关闭).
  • 内部实现.
    • New,如果类型有Finalize(),则在构造实例前,将该对象的指针放到终结链上(Object基类亦有Finalize(),但是已被忽略,仅当本类型/Object外的基类中定义有Finalize时才算).
    • GC开始后,将终结链上有的不可达对象指针移动到队列上,且对象变为可达不能回收,CLR有一高优先级线程调用Finalize当队列非空时将其唤醒运行.在下次GC,可以回收刚刚变为可达的对象.但是由于代提升,可能大于2次后,GC才将其回收.
  • 确定的GC(Dispose).
    • 由于Finalize的不定,使用释放模式.接口Idisposable{void Dispose();}但仍有GC负责,释放时机仍不定.
    • 如果类定义了一个实现了该接口的成员字段,则类本身亦需要实现.可以多次调用Dispose/Close(非首次直接返回).
    • Dispose(Bool)CLR调用使用False以使Dispose不再调用其引用的其他托管对象的方法,因为该对象可能已经被Finalize.手动时True可以调用其引用的对象.Dispose为该模式必有的,Close为可选.
    • Dispose只是在一个确定时刻对对象清理资源,而对对象的生命周期没有影响.
    • 在其后调用对象方法会有异常,但是由于对象内存仍然存在,所以不会崩溃.
    • 一般不应该使用Dispose而交由GC自动运行.在需显式释放资源且希望对象从终结链中删除以防止其代提升时使用它.
    • Using语句:初始化一个Object,转化为Idispose调用Dispose(),所以只能用于实现了Idispose接口的类型,可初始化多个同类型的变量.已初始化的对象由于GC不保证多对象上的Finalize顺序,所以应显式调用以避免对象已被终止还在使用.
  • 复苏.
    • 终结可达队列上对象被复苏,且其引用亦复苏.
    • FinalizeXXX.static_data=this(重复引用该对象,防止其死亡),GC.ReregisterForFinalize(this)(为了在使用一次该对象后,调用一次Finalize,将对象指针添加到终结链表上以使用).
    • 创建一个托管进程后,在内存中:System/Shared/Default Damain +一系列Heap.
  • 值/引用类型.
    • 值类型.变量中存放的是真正的数据,超出变量的作用域时内存释放,不可为空.
    • 引用类型.变量中存放的是指向数据的指针,由GC负责内存的是否,可为空.
  • CLR有可空值类型.
    • 其本身为轻量级值类型,会造成大量的IL产生.Nullable<T>{bool HasValue,T Value},其可在非Null时拆箱,装箱时会分配内存(HasValue字段).
    • 装箱:在托管堆上分配内存(各字段和+两额外),值类型字段拷贝至其内,返回对象指针.
    • 拆箱:实为获取一个指向对象中元素值类型的指针.之后会复制字段.
原文地址:https://www.cnblogs.com/robyn/p/3788020.html