原文作如下总结。
简介
.Net垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存
一些基础
在公共语言运行时 (CLR) 中,垃圾回收器用作自动内存管理器。
-
作为一名应用程序开发人员,<u style="box-sizing: border-box;">你只能使用虚拟地址空间,请勿直接操控物理内存</u>。 垃圾回收器为你分配和释放托管堆上的虚拟内存。
虚拟内存分为
可用(即可分配)
保留(即已分配)
提交(即已指派给物理内存)三种状态。
-
发生垃圾回收的三种情况:
物理内存不够了
托管堆上已分配的对象使用的内存过高
GC.Collect被调用。
在垃圾回收器由 CLR 初始化之后,它会分配一段内存用于存储和管理对象。 此内存称为托管堆(与操作系统中的本机堆相对):一个用于小型对象(小型对象堆或 SOH),一个用于大型对象(大型对象堆)。每个托管进程都有一个托管堆。 进程中的所有线程都在同一堆上给对象分配内存。
托管堆视为两个堆的累计:大对象堆和小对象堆。
垃圾回收器分配的段大小是动态的。 应用程序不应假设特定段的大小或依赖于此大小,也不应尝试配置段分配可用的内存量。
堆以代数(共3代)来组织,这样就能有效管理短生存对象和长生存对象。垃圾回收最常发生在第0代(包含短生存期对象),当回收第2代时会连带它所有代一起回收。垃圾回收中未回收的对象也称为幸存者,并会被提升到下一代。
-
垃圾回收过程中发生了什么:
标记阶段,找到并创建所有活动对象的列表。
重定位阶段,用于更新对将要压缩的对象的引用。
压缩阶段,用于回收由死对象占用的空间,并压缩幸存的对象。
-
垃圾回收器使用以下信息来确定对象是否为活动对象:
堆栈根。 由实时 (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 的垃圾回收器来处理内存管理,释放托管资源。 但是,如果创建包括非托管资源的对象,则当你在应用中使用完非托管资源后,必须显式释放这些资源。 最常用的非托管资源类型是包装操作系统资源的对象,如文件、窗口、网络连接或数据库连接。 虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但无法了解如何发布并清理这些非托管资源。 如果你的类型使用非托管资源,则应执行以下操作:
实现释放模式。这就需要你去继承并实现 IDisposable.Dispose方法,来实现对非托管资源的明确性释放。这样当不再使用此对象时,类的使用者可以调用该方法释放资源。
如果上述情况下对象引用忘记调用 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
程序域监控