跳转至

托管堆与托管代码

托管堆:初始化新进程时,运行时将为进程保留一个连续的地址空间区域。 这个保留的地址空间被称为托管堆。 托管堆维护着一个指针,用它指向将在堆中分配的下一个对象的地址。 - 从托管堆中分配内存要比非托管内存分配速度快。由于运行时通过向指针添加值为对象分配内存,因此它几乎与从堆栈分配内存的速度一样快(可以看作是一个内存池)。

应用程序的根

每个应用程序都有一组根。每个根要么引用托管堆上的对象,要么设为 null。  应用程序的根包括:  - 静态字段  - 线程堆栈上的局部变量和参数  - 以及 CPU 寄存器

释放过程

标记:垃圾回收器可以访问实时 (JIT) 编译器和运行时维护的活动根节点列表。 使用此列表,它会检查应用程序的根,在此过程中会创建一个图形,其中包含可从根访问的所有对象。

图形中不存在的对象无法从应用程序的根目录访问。 垃圾回收器会考虑无法访问的对象垃圾,并释放为其分配的内存。

访问无效空间:在回收中,垃圾回收器检查托管堆,查找无法访问对象所占据的地址空间块。    内存复制与整理:发现每个无法访问的对象时,它使用内存复制函数压缩内存中可访问的对象,释放分配给不可访问对象的地址空间块。   指针更正: - 压缩可访问对象的内存后,垃圾回收器会进行必要的指针更正,以便应用程序的根指向其新位置中的对象。 - 它还将托管堆的指针放置在最后一个可访问对象之后。

特别的:大型对象单独分配,不压缩 为了提高性能,运行时为单独的堆中的大型对象分配内存。 垃圾回收器会自动释放大型对象的内存。 但是,为了避免在内存中移动大型对象,不会压缩此内存。

分代与性能

1.基本方案的原理

  1. 压缩托管堆的一部分内存要比压缩整个托管堆速度快
  2. 较新的对象将具有较短的生存期,较旧的对象具有更长的生存期
  3. 较新的对象往往彼此相关,同时由应用程序访问。

2. C#的分代方式

第0代: 第1代: 第二代:

3. 分代回收方案

  • 触发与检查:当第0代内存已满时,垃圾回收器会执行回收。垃圾回收器首先检查第 0 代中的对象,而不是托管堆中的所有对象。
  • 回收与压缩:垃圾回收器执行第 0 代回收后,它会压缩可访问对象的内存。
  • 升级:然后,垃圾回收器升级这些对象,并考虑第 1 级托管堆的这一部分。
  • 剩下的托管堆部分继续视作第 0 代,继续为第 0 代中的新对象分配内存。

更多的回收: 如果第 0 代集合未回收足够的内存,以便应用程序成功完成其创建新对象的尝试,则垃圾回收器可以执行第 1 代(第 2 代)的回收。

清理非托管内存资源

1. 实现清理模式

  • 提供IDisposable.Dispose的确定性释放。
  • 在类型使用者忘记调用 Dispose 的情况下,请提供一种方法来释放非托管资源。 可以通过两种方式来执行此操作:
    • 使用安全句柄包装非托管资源建议)。安全句柄派生自 System.Runtime.InteropServices.SafeHandle 抽象类,并包含可靠的 Finalize 方法
    • 定义终结器(不安全,复杂容易出错):但dispose无法调用。Finalizer会启用对非托管资源的确定性释放。

      (正确实现Dispose方法时,安全句柄的Finalize方法或object.Finalize方法的重写会在未调用Dispose方法的情况下阻止清理资源)

2.实现Disposable方法

  • Dispose非虚拟函数:IDisposable必须实现
  • Dispose(bool):虚拟方法

Dispose() 方法

  • Dispose(true) : 参数用来指示方法调用来自Dispose方法,还是析构函数。
  • 抑制终结器:GC.SuppressFinalize(this);

Dispose(bool disposing) 方法重载

disposing 代表来自方法调用还是析构函数(false) - 判断_disposed:对象已释放,则为条件返回块 - 释放内部托管资源的条件块(disposing为true): - 实现 IDisposable 的托管对象。 如果disposing为true,实现IDisposable的托管对象级联释放。(若为SafeHandle,则在此调用其Dispose) - 使用大量内存或消耗稀缺资源的托管对象。 大型托管对象引用分配给 null,这样相比非确定性回收更快 - 释放非托管资源,无论disposing如何 - 结束后,设置_disposed 字段为true,代表已经释放

级联释放带调用

class Foo : IDisposable
{
    private IDisposable _bar;

    public void Dispose() => _bar.Dispose();
}

实现释放模式

所有非密封类(或未修改为 NotInheritable的 Visual Basic 类)都应被视为潜在的基类,应当实现 - 调用Dispose方法的Dispose(bool)方法 - 执行实际清理的Dispose(bool)方法 - 从包装非托管资源的SafeHandle派生的类,或对Object.Finalize的重写(终结器设置Disposable(false))

3.使用实现 IDisposable 的对象

完成使用实现 IDisposable 的对象后,调用对象的 Dispose 或 DisposeAsync 实现以显式执行清理。 可通过以下两种方式之一执行此操作:

  • 使用 C# using 语句或声明(Using 在 Visual Basic 中)。
  • 通过实现try/finally块,并在Dispose中调用DisposeAsyncfinally方法。

try finally

finally默认中默认隐式调用 Object?.Dispose();

using语句

类似finally隐式调用Object?.Dispose。