欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

C#/.Net 的托管堆和垃圾回收

发布时间:2023/12/10 37 豆豆
生活随笔 收集整理的这篇文章主要介绍了 C#/.Net 的托管堆和垃圾回收 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

1、托管堆基础

资源包括包括:文件、内存缓冲区、网络连接等。

以下是访问一个资源所需的步骤:

  • 调用IL指令newobj,为代表资源的类型分配内存(一般使用C# new操作符来完成)。
  • 初始化内存,设置资源的初始状态并使资源可用。类型的实例构造器负责设置初始状态。
  • 访问类型的成员来使用资源(有必要可以重复)。
  • 摧毁资源的状态以进行清理。(为了简化编程,开发人员经常使用的大多数类型都不需要这个步骤)。
  • 释放内存。垃圾回收器独自负责这一步。
  • 2、托管堆分配资源

    CLR要求所有对象都从托管堆分配。进程初始化时,CLR划出一个地址空间区域作为托管堆,CLR还要维护一个指针,我把它称作NextObjPtr。该指针指向下一个对象在堆中的分配位置。

            你的应用程序的内存受进程的虚拟地址空间的限制。32位进程最多能分配1.5GB,64位进程最多能分配8TB。

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

    1. 计算类型的字段(以及从基类型继承的字段)所需的字节数
    2. 加上对象的开销所需的字节数。每个对象都有两个开销字段:类型对象指针和同步块索引。对于32位应用程序,这两个字段各自需要32位,所以每个对象都要8字节。对于64位应用程序,这两个字段各自需要64位,所以每个对象要增加16个字节。
    3. CLR检查区域中是否有分配对象所需的字节数。如果托管堆有足够的可用空间,就在NetxObjPtr指针指向的地址处放入对象,为对象分配的字节会被清零。接着调用类型的构造器(为this参数传递NextObjPtr),new操作符返回对象引用。就在返回这个对象引用之前,NextObjPtr指针的值会加上这个对象占用的字节数来得到一个新值,即下个对象放入托管堆时的地址。

    3、垃圾回收算法

     应用程序调用new操作符创建对象时,可能没有足够地址空间来分配该对象,发现空间不够,CLR就执行垃圾回收。

            至于对象生存期的管理,有的系统采用的是某种引用计数算法。在这种系统中,堆上的每个对象都维护着一个内存字段来统计程序中多少“部分”正在使用对象。随着每一“部分”到达代码中某个不再需要对象的地方,就递减对象的计算字段。计数字段成0,对象就可以从内存中删除了。许多引用计数系统最大的问题是处理不好循环引用

                 鉴于引用计数垃圾回收器算法存在的问题,CLR改为使用一种引用跟踪算法。引用跟踪算法中关心引用类型的变量,因为只有这种变量才能引用堆上的对象;值类型变量直接包含值类型实例。引用类型变量可在许多场合使用,包括静态和实例字段,或者方法的参数和局部变量。我们将所有引用类型的变量都称为根

               1、CLR开始GC时,首先暂停进程中的所有线程。这样可以防止线程在CLR检查期间访问对象并更改其状态。     

               2、然后,CLR进入GC的标记阶段。在这个阶段,CLR遍历堆中的所有对象,将同步块索引字段中的一位设为0。这表明所有对象都应删除。   

               3、然后,CLR检查所有的活动根,查看它们引用了哪些对象。这正是CLR的GC称为引用跟踪GC的原因。如果一个根包含NULL,CLR忽略这个根并继续检查下一个根。

               4、任何根如果引用了堆上的对象,CLR都会标记那个对象,也就是将该对象的同步块索引中的位设为1, 标记过程会持续,直至应用程序的所有跟所有检查完毕。       

               5、检查完毕后,堆中的对象要么已标记,要么未标记。已标记的对象不能被垃圾回收,因为至少有一个根在引用它。我们说这种对象是可达的。因为应用程序代码可通过仍在引用它的变量抵达(访问)它。未标记的对象是不可达的。因为应用程序中不存在使对象能被再次访问的根。

                 6、CLR知道哪些对象可以幸存,哪些可以删除后,就进入GC的压缩(不是那个压缩,类似于碎片整理)阶段。在这个阶段。CLR对堆中标记的对象进行“乾坤大挪移”

                7、在内存中移动了对象之后有一个问题亟待解决。引用幸存对象的根现在引用的还是对象最初在内存中的位置,而非移动后的位置。被暂停的线程恢复执行时,将访问旧的内存位置,会造成内存损坏。这显然是不能容忍的,所以作为压缩阶段的一部分,CLR还要从每个根减去所引用对象在内存中偏移的字节数。这样就能保证每个根还是引用和之前一样的对象,只是对象在内存中变换了位置。

                8、压缩阶段完成后,CLR恢复应用程序的所有线程。

    重要提示:   静态字段引用的对象一直存在,直到用于加载类型的AppDomain卸载为止。内存泄漏的一个常见原因就是让静态字段引用某个集合对象,然后不停地往集合添加数据项。静态字段使集合对象一直存活,而集合对象使所有数据项一直存活。因此应该尽量避免使用静态字段。(或者参照前面的玩法,当我们不用静态变量的时候,可以立马置为null,那么垃圾就会被回收)。

    4、代:提升性能

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

    • 对象越新,生存期越短
    • 对象越老,生存期越长
    • 回收堆的一部分,速度快于回收整个堆

    第一个假设是越新的对象活的越短。因此,第0代包含跟多垃圾的可能性很大,能回收更多的内存。由于忽略了第1代中的对象,所以加快了垃圾回收速度。

    第二个假设越老的对象活的越长。也就是说,第1代对象在应用程序中很有可能继续可达(没被回收)的。如果垃圾回收器检查第1代中的对象,很有可能找不到多少垃圾。

    由于第0代已满,所以必须开始垃圾回收。但这一次垃圾回收器发现第1代占用了太多内存,以至于用完了预算。 由于前几次对第0代进行回收时,第1代可能已经有许多对象变得不可达(该回收)。所以这次垃圾回收器决定检查第1代和第0代的所有对象。 两代都被垃圾回收后,  就出现了第2代了。 空的是0代, 0代幸存者变为1代,1代幸存者变为2代。

               托管堆只支持三代:第0代, 第1代,第2代。

    CLR 的垃圾回收器是自动调节的:

    1、如果垃圾回收器发现在回收第0代后存活下来的对象很少,就可能减少第0代的预算。已分配空间的减少意味着垃圾回收将更频繁地发生。

    2、另一方面,如果垃圾回收器回收了第0代,发现还有很多对象存活,没有多少内存被回收就会增加第0代的预算。

    3、垃圾回收器用类似的  启发式算法 调整第1代 和 第2代的预算。

    5、垃圾回收触发条件

    1、CLR在检测第0代超过预算时会触发一次GC,这是GC最常见的触发条件,还有其它的触发如下:

    2、代码显示调用System.GC的静态Collect方法,  大多时候都要避免调用这个方法;最好让垃圾回收器自行斟酌执行,让它根据应用程序的行为调整各个代的预算。

    3、Windows报告低内存情况

    4、CLR正在卸载AppDomain

    5、CLR正在关闭

     

    官方的介绍:(良心发现 有中文)

              https://docs.microsoft.com/zh-cn/dotnet/articles/standard/garbagecollection/

    6、 扩展阅读

    unity在线文档Understanding the managed heap

    https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html

    Optimizing garbage collection in Unity games

    https://unity3d.com/cn/learn/tutorials/topics/performance-optimization/optimizing-garbage-collection-unity-games?playlist=44069

    C# Garbage Collection Tutorial

    https://stackify.com/c-garbage-collection/

    Memory Management/Stacks and Heaps

    https://en.wikibooks.org/wiki/Memory_Management/Stacks_and_Heaps

    Lambda Expressions vs. Anonymous Methods

    https://blogs.msdn.microsoft.com/ericlippert/2007/01/10/lambda-expressions-vs-anonymous-methods-part-one/

    推荐书籍:

    《垃圾回收的算法与实现》

    《C#/.Net 的托管堆和垃圾回收》

    《CLR via C#》

     

    转载自:https://blog.csdn.net/u010019717/article/details/66975553

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/u010019717/article/details/66975553

    总结

    以上是生活随笔为你收集整理的C#/.Net 的托管堆和垃圾回收的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。