托管堆与托管代码¶
托管堆:初始化新进程时,运行时将为进程保留一个连续的地址空间区域。 这个保留的地址空间被称为托管堆。 托管堆维护着一个指针,用它指向将在堆中分配的下一个对象的地址。 - 从托管堆中分配内存要比非托管内存分配速度快。由于运行时通过向指针添加值为对象分配内存,因此它几乎与从堆栈分配内存的速度一样快(可以看作是一个内存池)。
应用程序的根¶
每个应用程序都有一组根。每个根要么引用托管堆上的对象,要么设为 null。 应用程序的根包括: - 静态字段 - 线程堆栈上的局部变量和参数 - 以及 CPU 寄存器。
释放过程¶
标记:垃圾回收器可以访问实时 (JIT) 编译器和运行时维护的活动根节点列表。 使用此列表,它会检查应用程序的根,在此过程中会创建一个图形,其中包含可从根访问的所有对象。
图形中不存在的对象无法从应用程序的根目录访问。 垃圾回收器会考虑无法访问的对象垃圾,并释放为其分配的内存。
访问无效空间:在回收中,垃圾回收器检查托管堆,查找无法访问对象所占据的地址空间块。 内存复制与整理:发现每个无法访问的对象时,它使用内存复制函数压缩内存中可访问的对象,释放分配给不可访问对象的地址空间块。 指针更正: - 压缩可访问对象的内存后,垃圾回收器会进行必要的指针更正,以便应用程序的根指向其新位置中的对象。 - 它还将托管堆的指针放置在最后一个可访问对象之后。
特别的:大型对象单独分配,不压缩 为了提高性能,运行时为单独的堆中的大型对象分配内存。 垃圾回收器会自动释放大型对象的内存。 但是,为了避免在内存中移动大型对象,不会压缩此内存。
分代与性能¶
1.基本方案的原理¶
- 压缩托管堆的一部分内存要比压缩整个托管堆速度快
- 较新的对象将具有较短的生存期,较旧的对象具有更长的生存期
- 较新的对象往往彼此相关,同时由应用程序访问。
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中调用DisposeAsync或finally
方法。
try finally¶
finally默认中默认隐式调用 Object?.Dispose();
using语句¶
类似finally隐式调用Object?.Dispose。