美文网首页
.Net垃圾回收

.Net垃圾回收

作者: JervieQin | 来源:发表于2019-03-09 21:13 被阅读0次

    原文作如下总结。

    简介

    .Net垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存

    一些基础

    1. 公共语言运行时 (CLR) 中,垃圾回收器用作自动内存管理器。

    2. 作为一名应用程序开发人员,<u style="box-sizing: border-box;">你只能使用虚拟地址空间,请勿直接操控物理内存</u>。 垃圾回收器为你分配和释放托管堆上的虚拟内存。

      虚拟内存分为

      • 可用(即可分配)

      • 保留(即已分配)

      • 提交(即已指派给物理内存)三种状态。

    3. 发生垃圾回收的三种情况:

      • 物理内存不够了

      • 托管堆上已分配的对象使用的内存过高

      • GC.Collect被调用。

    4. 在垃圾回收器由 CLR 初始化之后,它会分配一段内存用于存储和管理对象。 此内存称为托管堆(与操作系统中的本机堆相对):一个用于小型对象(小型对象堆或 SOH),一个用于大型对象(大型对象堆)。每个托管进程都有一个托管堆。 进程中的所有线程都在同一堆上给对象分配内存。

    托管堆视为两个堆的累计:大对象堆和小对象堆。

    1. 垃圾回收器分配的段大小是动态的。 应用程序不应假设特定段的大小或依赖于此大小,也不应尝试配置段分配可用的内存量。

    2. 堆以代数(共3代)来组织,这样就能有效管理短生存对象和长生存对象。垃圾回收最常发生在第0代(包含短生存期对象),当回收第2代时会连带它所有代一起回收。垃圾回收中未回收的对象也称为幸存者,并会被提升到下一代。

    3. 垃圾回收过程中发生了什么:

      • 标记阶段,找到并创建所有活动对象的列表。

      • 重定位阶段,用于更新对将要压缩的对象的引用。

      • 压缩阶段,用于回收由死对象占用的空间,并压缩幸存的对象。

    4. 垃圾回收器使用以下信息来确定对象是否为活动对象

      • 堆栈根。 由实时 (JIT) 编译器和堆栈查看器提供的堆栈变量。 请注意,JIT 优化可以延长或缩短报告给垃圾回收器的堆栈变量内的代码的区域。

      • 垃圾回收句柄。 指向托管对象且可由用户代码或公共语言运行时分配的句柄。

      • 静态数据。 应用程序域中可能引用其他对象的静态对象。 每个应用程序域都会跟踪其静态对象。

        在垃圾回收启动之前,除了触发垃圾回收的线程以外的所有托管线程均会挂起。

    大型对象堆(LOH)

    如果对象大于或等于 85,000 字节,将被视为大型对象。 此数字根据性能优化确定。 对象分配请求为 85,000 字节或更大时,运行时会将其分配到大型对象堆。

    小型对象始终在第 0 代中进行分配,或者根据它们的生存期,可能会提升为第 1 代或第 2 代。 大型对象始终在第 2 代中进行分配。大型对象属于第 2 代,因为只有在第 2 代回收期间才能回收它们。 回收一代时,同时也会回收它前面的所有代。

    执行GC的情形

    • 分配超出第 0 代或大型对象阈值。

      阈值是某代的属性。 垃圾回收器在其中分配对象时,会为代设置阈值。 超出阈值后,会在该代上触发 GC。

      这是典型情况,大部分 GC 执行都因为托管堆上的分配。

    • 调用 GC.Collect 方法。

      如果调用无参数 GC.Collect() 方法,或另一个重载作为参数传递到 GC.MaxGeneration,将会一起收集 LOH 和剩余的托管堆。

    • 系统处于内存不足的状况。

      垃圾回收器收到来自操作系统 的高内存通知时,会发生以上情况。 如果垃圾回收器认为执行第 2 代 GC 会有效率,它将触发第 2 代。

    手动清理非托管资源

    对于应用创建的大多数对象,可以依赖 .NET 的垃圾回收器来处理内存管理,释放托管资源。 但是,如果创建包括非托管资源的对象,则当你在应用中使用完非托管资源后,必须显式释放这些资源。 最常用的非托管资源类型是包装操作系统资源的对象,如文件、窗口、网络连接或数据库连接。 虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但无法了解如何发布并清理这些非托管资源。 如果你的类型使用非托管资源,则应执行以下操作:

    1. 实现释放模式。这就需要你去继承并实现 IDisposable.Dispose方法,来实现对非托管资源的明确性释放。这样当不再使用此对象时,类的使用者可以调用该方法释放资源。

    2. 如果上述情况下对象引用忘记调用 Dispose方法,你得为释放非托管资源做准备。方法有两种:

    • 使用安全句柄包装非托管资源(推荐)。安全句柄派生自System.Runtime.InteropServices.SafeHandle 类并包含可靠的 Finalize 方法:System.Object声明的一个虚方法Finalize(也称为终结器),它在GC回收对象的内存之前由GC调用,并且可以被覆盖以释放非托管资源。 】。 在使用安全句柄时,只需实现 IDisposable 接口并在 Dispose 实现中调用安全句柄的 IDisposable.Dispose 方法。 如果未调用安全句柄的Dispose 方法,则垃圾回收器将自动调用安全句柄的终结器。

    • 重写 Object.Finalize 方法。 当类型使用者无法调用 IDisposable.Dispose以确定性地释放非托管资源时,终结器会启用对非托管资源的非确定性释放。 但是,由于对象终止是一项复杂且易出错的操作,建议你使用安全句柄而不是提供你自己的终结器。

    完成上述内容后,对象引用就可以直接调用 IDisposable.Dispose 实现以释放非托管资源使用的内存。另外值得注意的是, 在正确实现 Dispose 方法后,安全句柄的 Finalize 方法或 Object.Finalize方法的重写会在未调用 Dispose 方法的情况下阻止清理资源。

    GC优缺点

    在公共语言运行时 (CLR) 中,垃圾回收器用作自动内存管理器。 它提供如下优点:

    • 使你可以在开发应用程序时不必释放内存。

    • 有效分配托管堆上的对象。

    • 回收不再使用的对象,清除它们的内存,并保留内存以用于将来分配。 托管对象会自动获取干净的内容来开始,因此,它们的构造函数不必对每个数据字段进行初始化。

    • 通过确保对象不能使用另一个对象的内容来提供内存安全。

    缺点:

    开销影响程序性能。

    减少GC开销的策略

    • 只在必要时显示调用GC

    • 尽量减少临时对象的使用(循环中的临时变量、对象缓存)

    • 对象不用最好显式置为null(即时置空对资源的引用)

    • 字符串拼接使用StringBuilder

    • 能用基本值类型就不用引用类型

    • 尽量减少不必要的静态变量

    • 分散对象的创建或删除时间

    性能分析工具(待了解)

    • 内存计数器

    • SOS调试

    • 垃圾回收ETW事件

    • 分析API

    • 程序域监控

    相关文章

      网友评论

          本文标题:.Net垃圾回收

          本文链接:https://www.haomeiwen.com/subject/xpwepqtx.html