算法分类

  • 引用计数
  • 引用跟踪

引用计数:每个对象维护一个内存字段来统计程序中有多少部分正在使用该对象,处理不好循环引用。
引用跟踪:标记可达对象,清除不可达对象。

CLR 采用引用跟踪算法,托管堆维护着一个指针 NextObjPtr,它指向下一个对象在堆中的分配位置。
GC 时,会暂停进程中所有线程,防止 CLR 检查期间对象状态变更。
一旦根离开作用域,它的引用对象就会变得不可达,GC 会回收其内存。

标记清除

标记

  • 通过活动根标记它引用的堆对象,然后递归标记堆对象中引用的对象;
  • 如果对象被标记过,就停止递归标记,避免死循环;

清除

  • 清除无标记的对象:普通对象直接回收内存,有终结器的对象单独回收,清除之后内存占用会变得不连续。

压缩

  • 挪动幸存的对象,使它们占用连续内存空间;
  • 更新活动根引用的对象地址;

特点

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

工作原理

  1. 初始添加的对象为 0 代,同时给 gen0 一个预估的内存大小阈值;
  2. 当初始申请的堆内存不足以分配给新对象时,就会触发 GC;
  3. 回收和压缩完,幸存的对象会被看成 gen1 对象,此时 gen0 被认为是空的;
  4. 新对象会被分配到 gen0;
  5. 如果又遇到 gen0 空间不足时,发现 gen1 老数据空间占用低于阈值,就转而针对 gen0 对象进行垃圾回收处理;
  6. 对于老对象中字段引用新对象情况,垃圾回收器利用 JIT 编译器内部机制,在对象引用字段发生变化时,设置一个标志位,表明老对象在上一轮垃圾回收后更新过,只有发生变化的老对象才需要检查是否引用了 gen0 对象;
  7. 根据假设,老对象中可能不值得去进行垃圾回收,所以就算 1 代存在垃圾对象,也可能会保留下来;
  8. 一顿操作后,gen0 对象也会被认为是 gen1 对象了,gen0 再次被认为是空的;
  9. gen1 缓慢增长,当某次 0 代空间又不足,触发 GC,发现 gen1 也超出容量阈值,就对 gen0 和 gen1 的对象都进行垃圾回收;
  10. gen1 变 gen2,gen0 变 gen1,gen0 被认为是空的;
  11. 默认托管堆只支持 3 代(MaxGeneration() 返回 2);

补充说明

  • 如果垃圾回收 0 代存活对象很少,可能会减少 0 代容量预算;
  • 如果垃圾回收 0 代发现没多少内存被回收,就会增加 0 代容量预算;
  • 如果没有回收到足够内存,垃圾回收器会执行一次完整回收;
  • 如果还不够,会抛出 OOM(OutOfMemoryException)异常;

触发 GC

  • 0 代内存不足;
  • 显式 GC.Collect();
  • Windows 报告低内存情况;
  • CLR 正在卸载 AppDomain;
  • CLR 正在关闭;

大对象:大于等于 85000 字节,被认为是大对象。对于大对象来说,又称为 gen3。
大对象堆:存放大对象,称为 gen3。第 3 代和第 2 代一起参与垃圾回收。

GC 模式

  • 工作站,默认,适合客户端应用程序。GC 时延低,防 UI 线程明显假死;
  • 服务器,服务器端应用程序,优化吞吐量和资源利用能力;

GC 子模式

  • 并发,默认,后台线程在应用程序运行时并发标记对象。
  • 非并发。

延迟模式

GC 时会暂停应用程序中正在运行的线程,线程被暂停的时间称为延迟。

模式 说明
Batch 服务器模式默认值 禁后台 GC(并发 GC)。用于服务器端或无 UI 的应用程序。会替代 gcConcurrent 设置。
Interactive 工作站模式默认值 用于有 UI 的大多数应用程序。如果托管了某个应用,则会优先考虑托管进程的垃圾回收器设置。
LowLatency (短期)低延迟模式 禁止 2 代回收,允许 0 代 1 代回收,只能在短时间内使用,仅工作站可用。
SustainedLowLatency 长期低延迟模式 禁止 2 代前台回收,允许 0 代 1 代回收和 2 代后台回收,可以长时间使用,工作站和服务器都可用。
NoGCRegion 在程序执行关键路径时将 GC 线程挂起 不能将该值直接赋值给 GCLatencyMode 属性,通过调用 GC.TryStartGCRegion 方法开始,调用 GC.EndGCRegion 方法结束。

内存泄露

  • 不正确的使用静态字段,导致大量数据无法被 GC 释放;
  • 没有正确执行 Dispose(),非托管资源没有得到释放;
  • 不正确的使用终结器 Finalize(),导致无法正常释放资源;
  • 其他不正确的引用,导致大量托管对象无法被 GC 释放;

托管和非托管资源

托管资源

指的是 .NET 可以自动进行回收的资源,主要是指托管堆上分配的内存资源。

非托管资源

指的是 .NET 不知道如何回收的资源,最常见的是包装操作系统资源的对象。
例如:文件,窗口,网络连接,数据库连接,图标等。通过调用 IDisposable.Dispose 方法来回收非托管资源。