专栏首页程序员与猫.NET 对象生命周期

.NET 对象生命周期

  • GC 垃圾回收

.NET Framework 的垃圾回收器管理应用程序的内存分配和释放。每次您使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行库就会继续为新对象分配空间。但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。在内存大于 2GB 的服务器中,可能需要在 boot.ini 文件中指定 /3GB 开关,以避免当内存仍可供系统使用时出现明显的内存不足问题。当使用非托管资源时,需要构造一个用完后清理自身的类,这时需要编写代码来进行垃圾回收。

  • 将对象引用设置为空

在C#中将对象引用设置为空并不意味着强制垃圾回收立即启动,唯一实现的是显示的取消了引用和之前所指向对象之间的连接,不管怎么样,这么做也不会有什么害处。

  • 应用程序根

根就是一个存储位置,其中保存着对托管堆上一个对象的引用。在垃圾回收过程中,运行库检查堆上的对象,判断应用程序是否仍然可以访问它们,即对象是否还是有根的。

    • 根的类别
      • 全局对象的引用(C#中不允许,但CIL代码允许分配全局对象)
      • 静态对象和字段的引用
      • 应用程序代码库中的局部对象引用
      • 传递进一个方法的对象参数的引用
      • 等待被终结的对象的引用
      • 任何引用对象的CPU寄存器
  • 延迟对象初始化

当一次实例化大量对象,会大大增加垃圾回收器的压力,但又不是所有的对象都立马需要使用,这时可以使用Lazy<>延迟对象实例化。

  • 内存管理规则
    • 使用new关键字实例化类对象分配在托管堆上,然后就不用再管它了。
    • 如果托管堆没有足够的内存来分配所请求的对象,就会进行垃圾回收。
    • 重写Finalize()唯一的原因是,C#类使用了非托管资源。
    • 如果对象支持IDisposable则总是要对任何直接创建的对象调用Dispose(),应该认为如果类设计者选择支持Dispose方法,这个类型就需要执行清除工作。
  • 强制垃圾回收

垃圾回收 GC 类提供 GC.Collect 方法,您可以使用该方法让应用程序在一定程度上直接控制垃圾回收器。通常情况下,您应该避免调用任何回收方法,让垃圾回收器独立运行。在大多数情况下,垃圾回收器在确定执行回收的最佳时机方面更有优势。但是,在某些不常发生的情况下,强制回收可以提高应用程序的性能。当应用程序代码中某个确定的点上使用的内存量大量减少时,在这种情况下使用 GC.Collect 方法可能比较合适。例如,应用程序可能使用引用大量非托管资源的文档。当您的应用程序关闭该文档时,您完全知道已经不再需要文档曾使用的资源了。出于性能的原因,一次全部释放这些资源很有意义。

在垃圾回收器执行回收之前,它会挂起当前正在执行的所有线程。如果不必要地多次调用 GC.Collect,这可能会造成性能问题。您还应该注意不要将调用 GC.Collect 的代码放置在程序中用户可以经常调用的点上。这可能会削弱垃圾回收器中优化引擎的作用,而垃圾回收器可以确定运行垃圾回收的最佳时间。

  • 需要强制垃圾回收的场景
    • 应用程序将进入一段代码,后者不希望被可能的垃圾回收中断。
    • 应用程序刚刚分配非常多的对象,你想尽可能多地删除已获得的内存。

  • 对象的代

CLR试图寻找不可访问对象时不会逐个检查托管堆上的每个对象,因为这样做会浪费大量的时间。为了优化这个过程,堆上的每个对象都被指定为属于某个代,代是垃圾回收器区分内存区域的逻辑视图,代的设计思路很简单,对象在堆上的存在时间约长就越应该保留。每次从0代开始检查释放内存空间,当空间不足时检查下一个代。

对象在执行一次垃圾回收之后,会进入到下一代。也就是说如果在第一次执行垃圾回收时,存活下来的对象会进入第1代,如果在第2次垃圾回收之后该对象仍然没有被当作垃圾回收掉,它就会成为第2代对象,2代对象就是最老的对象不会在提升代数。

当某代垃圾回收执行时,会同时执行更年轻代的垃圾回收。比如,当1代垃圾回收时会同时回收1代和0代的对象,当2代垃圾回收时会执行1代和0代的回收。

    • 第0代

没有被标记为回收的新对象,通常对象是在0代就被回收的。

    • 第1代

上次垃圾回收未被回收的对象,被标记为回收,但因为有足够的内存空间而未被删除的。1代对象是常驻内存对象和马上消亡对象之间的一个缓冲区。

    • 第2代

在一次以上的垃圾回收后仍然没有被回收的对象。

  • 大对象

如果一个对象的大小超过85000byte,就认为这是一个大对象,这个数字是根据性能优化的经验得到的。当一个对象申请内存大小达到这个阀值,它就会被分配到大对象堆上。CLR垃圾回收器根据所占空间大小划分对象。大对象和小对象的处理方式有很大区别,比如内存碎片整理,在内存中移动大对象的成本是昂贵的。

从代的角度看,大对象属于第2代对象,因为只有在2代回收时才会处理大对象。

从物理存储角度看,对象分配在不同的托管堆上。一个内存分配请求就是将托管对象放到对应的托管堆上。如果对象的大小小于85000byte,它会被放置在SOH(小对象堆)上,否则会被放在LOH(大对象堆)上。

当触发垃圾回收时,垃圾回收器会在小对象堆做碎片整理,将存活下来的对象移动到一起。而对于大对象堆,由于移动内存的开销很大,CLR团队选择只是清除它们,将回收掉的对象组成一个列表,以便满足下次有大对象申请使用内存,相邻的垃圾对象会被合并成一块空闲的内存块。

需要时时留意的是在.Net中不会对大对象堆做碎片整理操作,因此如果你要分配大对象并不想他们被移动,你可以使用fixed语句。

  • 大对象的回收
    • 在程序代码中调用GC.Collect方法时,如果在调用GC.Collect方法是传入GC.MaxGeneration参数时,会执行所有代对象的垃圾回收,包括大对象堆的垃圾回收。
    • CLR自动进行垃圾回收时,如果垃圾回收算法认为第2代回收是有成效的会触发第2代垃圾回收,例如操作系统内存不足时。
    • 大对象和第2代对象是一起回收的,如果大对象或者第2代对象占用空间超过其阀值时,就会触发第2代对象和大对象的回收。
  • 大对象对性能的影响

如果是临时性的分配大对象,就需要很多的时间来运行垃圾回收,也就是说如果你持续的使用大对象然后又释放大对象对性能会有很大的负面影响。当回收大对象时又触发回收第2代对象,则对性能会产生更大的负面影响。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CLR和.Net对象生存周期

    潘成涛
  • CLR和.Net对象生存周期

    标签:GC .Net C# CLR 前言 对象的生存周期和垃圾回收一直是容易被我们忽略的知识点,因为我们现在高级语言编程平台太“智能”了,自动的异常处理,内存管...

    潘成涛
  • Elasticsearch 的一些关键概念

    我更喜欢把 Elasticsearch 作为一种 nosql 去理解,它的一些开发概念和 MongoDB 以及 Redis 没有太大的区别,不过了解 Elast...

    潘成涛
  • JVM(2)--一文读懂垃圾回收

    与其他语言相比,例如c/c++,我们都知道,java虚拟机对于程序中产生的垃圾,虚拟机是会自动帮我们进行清除管理的,而像c/c++这些语言平台则需要程序员自己手...

    帅地
  • Java垃圾回收机制(Garbage Collection)--笔记

    学习笔记摘自https://www.bilibili.com/video/av30023103/?p=67

    逆回十六夜
  • JVM之GC算法解读(四)

    在上一文《GC与类的生命周期》中,我简单介绍了与垃圾回收相关的一些知识概念,在本文当中,就来对GC所依赖和实现的算法进行深入的解读和理解。希望对大家有所帮助~

    23号杂货铺
  • Java虚拟机详解(三)------垃圾回收

      如果对C++这门语言熟悉的人,再来看Java,就会发现这两者对垃圾(内存)回收的策略有很大的不同。

    IT可乐
  • JVM系列第8讲:JVM 垃圾回收机制

    在第 6 讲中我们说到 Java 虚拟机的内存结构,提到了这部分的规范其实是由《Java 虚拟机规范》指定的,每个 Java 虚拟机可能都有不同的实现。其实涉及...

    陈树义
  • JVM总体概括一:让我们知道在什么样的平台上舞蹈

    比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问...

    哲洛不闹
  • Java进阶10 内存管理与垃圾回收

    整个教程中已经不时的出现一些内存管理和垃圾回收的相关知识。这里进行一个小小的总结。 Java是在JVM所虚拟出的内存环境中运行的。内存分为栈(stack)和堆(...

    Vamei

扫码关注云+社区

领取腾讯云代金券