2023年12月31日发(作者:)
垃圾回收
Framework 3.5 version
Author: Jerry Huang
录
1.
垃圾回收概述
2.
Finalize方法和析构函数
3.
弱引用
4.
被动回收
5.
滞后时间模式
6.
针对共享WEB宿主优化
7.
垃圾回收通知
8.
清理非托管资源
9.
参考 C#
析构函数
10.
参考 WeakReference类
11.参考
垃圾回收内部原理
.NET Framework 的垃圾回收器管理应用程序的内存分配和释放。每次您使用 new 运算符创建对象时,运行时都从托管堆为该对象分配内存。只要托管堆中有地址空间可用,运行时就会继续为新对象分配空间。但是,内存不是无限大的。最终,垃圾回收器必须执行回收以释放一些内存。垃圾回收器优化引擎根据正在进行的分配情况确定执行回收的最佳时间。当垃圾回收器执行回收时,它检查托管堆中不再被应用程序使用的对象并执行必要的操作来回收它们占用的内存。
本节介绍了垃圾回收器如何自动管理应用程序中托管对象的内存分配和释放。除此之外,本节还介绍了推荐的设计模式,以用来正确地清理应用程序创建的任何非托管资源。
说明:
在 .NET Framework 1.0 版中,公共语言运行时 (CLR) 具有一个用于大型对象堆的独立内存管理器。在某些情况下,该内存管理器不将未使用的内存返回给操作系统,在少数情况下,它还会使该内存不能垃圾回收。这样会由于虚拟地址空间碎片而导致内存分配失败。在 .NET
Framework 1.1 和 2.0 版中,大型对象堆由称为堆片段的连续内存区域组成,这些区域完全对齐以使虚拟内存碎片减到最少。在垃圾回收过程中,从大型对象中回收的空间被合并起来并置于自由列表中。只包含自由列表项的堆片段被释放,内存被返回给操作系统。对大型对象堆所做的这些更改有效消除了由这种形式的虚拟地址空间碎片导致的内存分配故障。
重要说明:
在内存大于 2GB 的服务器中,可能需要在 文件中指定 /3GB 开关,以避免当内存仍可供系统使用时出现明显的内存不足问题。
本节内容
Finalize 方法和析构函数
介绍 Finalize 方法和析构函数如何允许对象在垃圾回收器自动回收对象的内存之前执行必要的清理操作。
弱引用
介绍允许应用程序访问对象,同时也允许垃圾回收器收集该对象的功能。
被动回收
介绍如何立即或在下一最佳时间回收对象。
时间模式
介绍可确定垃圾回收侵入性的模式。
针对共享 Web 宿主优化
介绍在通过承载若干个小型网站共享的服务器上如何优化垃圾回收。
垃圾回收通知
介绍如何确定完整垃圾回收何时即将发生以及何时完成。
清理非托管资源
介绍推荐的清理非托管资源的设计模式。
方法和析构函数
对于您的应用程序创建的大多数对象,可以依靠 .NET Framework 的垃圾回收器隐式地执行所有必要的内存管理任务。但是,在您创建封装非托管资源的对象时,当您在应用程序中使用完这些非托管资源之后,您必须显式地释放它们。最常见的一类非托管资源就是包装操作系统资源的对象,例如文件、窗口或网络连接。虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。对于这些类型的对象,.NET Framework 提供 ze 方法,它允许对象在垃圾回收器回收该对象使用的内存时适当清理其非托管资源。默认情况下,Finalize 方法不执行任何操作。如果您要让垃圾回收器在回收对象的内存之前对对象执行清理操作,您必须在类中重写 Finalize 方法。
说明:
若要在 C# 中实现 Finalize 方法,您必须使用析构函数语法。在 .NET Framework 2.0 版中,Visual C++ 为实现 Finalize 方法提供了自己的语法,详见Destructors and Finalizers in
Visual C++中的介绍。在 .NET Framework 1.0 版和 1.1 版中,Visual C++ 与 C# 一样,也对 Finalize 方法使用析构函数语法。
垃圾回收器使用名为“终止队列”的内部结构跟踪具有 Finalize 方法的对象。每次您的应用程序创建具有 Finalize 方法的对象时,垃圾回收器都在终止队列中放置一个指向该对象的项。托管堆中所有需要在垃圾回收器回收其内存之前调用它们的终止代码的对象都在终止队列中含有项。
说明:
为 ive 方法提供的代码示例演示攻击性垃圾回收如何会导致终结器在已回收的对象的成员仍在执行时运行,以及如何使用 KeepAlive 方法来阻止这种情况的发生。
Finalize 方法不应引发异常,因为应用程序无法处理这些异常,而且这些异常会导致应用程序终止。
实现 Finalize 方法或析构函数对性能可能会有负面影响,因此应避免不必要地使用它们。用
Finalize 方法回收对象使用的内存需要至少两次垃圾回收。当垃圾回收器执行回收时,它只回收没有终结器的不可访问对象的内存。这时,它不能回收具有终结器的不可访问对象。它改为将这些
象的项从终止队列中移除并将它们放置在标为准备终止的对象列表中。该列表中的项指向托管堆中准备被调用其终止代码的对象。垃圾回收器为此列表中的对象调用 Finalize 方法,然后,将这些项从列表中移除。后来的垃圾回收将确定终止的对象确实是垃圾,因为标为准备终止对象的列表中的项不再指向它们。在后来的垃圾回收中,实际上回收了对象的内存。
示例:
对象A:没有终结器(Finalize)的不可访问对象。
对象B:有终结器(Finalize)的不可访问对象。
第一次回收:
托管堆
对象A
1.被回收
对象B
垃圾回收器
3. Call ze
4. delete object B
Finallization队列
对象B
2. Move
Freachable队列
对象B
第二次回收:
1.被回收
托管堆
对象B
垃圾回收器
Finallization队列
Freachable队列
如果应用程序的代码可以访问一个正由该程序使用的对象,垃圾回收器就不能收集该对象,那么,就认为应用程序对该对象具有强引用。
弱引用允许应用程序访问对象,同时也允许垃圾回收器收集相应的对象。如果不存在强引用,则弱引用的有限期只限于收集对象前的一个不确定的时间段。使用弱引用时,应用程序仍可对该对象进行强引用,这样做可防止该对象被收集。但始终存在这样的风险:垃圾回收器在重新建立强引用之前先处理该对象。
弱引用特别适合以下对象:占用大量内存,但通过垃圾回收功能回收以后很容易重新创建。
假设 Windows 窗体应用程序中的一个树视图向用户显示了复杂的选项层次结构。如果基础数据量很大,则用户使用应用程序中的其他部分时,在内存中保留该树会导致效率低下。
当用户切换到应用程序的其他部分时,可使用 WeakReference 类来创建对该树的弱引用,并销毁所有强引用。当用户切换回该树时,应用程序会尝试获得对该树的强引用,如果得到,就不必重新构造该树。
要对某个对象建立弱引用,请使用要跟踪的对象的实例创建一个 WeakReference。然后将 Target
属性设置为该对象,将该对象设置为 null。有关代码示例,请参见类库中的 WeakReference。
短弱引用和长弱引用
可创建短弱引用或长弱引用:
短
垃圾回收功能回收对象后,短弱引用的目标会变为 null。弱引用本身是托管对象,和任何其他托管对象一样需要经过垃圾回收。 短弱引用是 WeakReference 的默认构造函数。
长
调用对象的 Finalize 方法后,会保留长弱引用。这样,您就可以重新创建该对象,但该对象仍保持不可预知的状态。要使用长引用,请在 WeakReference 构造函数中指定 true。
如果对象的类型没有 Finalize 方法,则会应用短弱引用功能,该弱引用只在目标被收集之前有效,运行终结器之后可以随时收集目标。
强引用并重新使用该对象,请将 WeakReference 的 Target 属性强制转换为该对象的类型。如果 Target 属性返回 null,则表示对象已被收集;否则,您可继续使用该对象,因为应用程序已重新获得了对它的强引用。
使用弱引用的准则
仅在必要时使用长弱引用,因为在终止后对象的状态是不可预知的。
避免对小对象使用弱引用,因为指针本身可能和对象一样大,或者比对象还大。
不应将弱引用作为内存管理问题的自动解决方案,而应开发一个有效的缓存策略来处理应用程序的对象。
在大多数情况下,垃圾回收器可以确定执行回收的最佳时间,应让其独立运行。在某些不常发生的情况下,强制回收可以提高应用程序的性能。在这些情况下,可使用 Collect 方法强制垃圾回收,以引发垃圾回收。
当应用程序代码中某个确定的点上使用的内存量大量减少时,请使用 Collect 方法。例如,如果应用程序使用包含若干个控件的复杂对话框,则在对话框关闭时调用 Collect 可能会通过立即回收内存来提高性能。务必确保应用程序不会过于频繁地引发垃圾回收,否则当垃圾回收器无效率地尝试回收对象时,可能会使性能降低。Optimized 模式使垃圾回收器可以根据收集是否有效率来确定是否进行回收。
GC 回收模式
可以使用 t 方法重载,它使用 GCCollectionMode 值指定强制回收的行为,如下表所述。
成员
Default
Forced
说明
使用指定的设置作为正在运行的 .NET Framework 版本的默认垃圾回收配置。
强制立即执行垃圾回收。这等效于调用 t()。
Optimized 使垃圾回收器可以确定当前时间是否是回收对象的最佳时间。
垃圾回收器可能判定收集效率不够高,因此收集不合理,在这种情况下将返回而不回收对象。
若要回收对象,垃圾回收器必须停止应用程序的所有正在执行的线程。在某些情况下,例如当应用程序检索数据或者显示内容时,完整垃圾回收可能会在关键时刻进行,而且可能会影响性能。可以通过将 LatencyMode 属性设置为 GCLatencyMode 值之一来调整垃圾回收器的侵入性。
滞后时间指的是垃圾回收器侵入应用程序的时间。在低滞后时间期间,垃圾回收器在回收对象时较保守且侵入性较弱。第 2 代回收不常发生,这使得应用程序工作集随着时间而增长。因此,建议您在需要 LowLatency 模式时仅在短时间内使用该模式。否则,如果系统处于内存压力下,垃圾回收器将触发一次回收,这样会暂时暂停应用程序并中断对时间要求很急的操作。
低滞后时间模式应当用于以下应用程序:包含运行时间较短的代码块,而且必须在运行时中断最少的情况下运行。虽然 LowLatency 模式设计用于存在某些时间限制的情况中,但它并不是针对存在严格实时限制的情况的解决方案。
下表列出了 GCLatencyMode 值适用的应用程序方案。
滞后时间模式
Batch
应用程序方案
适用于没有 UI 或服务器端操作的应用程序。
Interactive 适用于多数有 UI 的应用程序。
LowLatency 适用于在垃圾回收器的中断可以中断时具有时效性短期操作的应用程序。例如,执行动画呈现或数据采集功能的应用程序。
默认垃圾回收模式
如果未指定 LatencyMode 属性,则默认模式为并发工作站垃圾回收。该模式取决于两个运行时配置设置的值:
如果启用,则此设置指定公共语言运行时在单独的线程上运行工作站垃圾回收以支持并发操作。默认情况下会启用此设置。
如果启用,则此设置指定公共语言运行时运行服务器垃圾回收;如果未启用,则运行工作站垃圾回收。只能在具有两个或更多处理器的计算机上启用服务器垃圾回收。默认情况下不启用此选项。
如果启用此设置,则自动禁用
GCLatencyMode 的默认值如下:
在启用
在禁用
说明:
如果应用程序在实现 Intel Itanium 体系结构的 64 位系统(以前称为 IA-64)上运行 WOW64
x86 仿真程序,则在这些应用程序中不支持并发垃圾回收。有关在 64 位 Windows 系统上使用 WOW64 的更多信息,请参见 Running 32-bit Applications(运行 32 位应用程序)。
低滞后时间使用准则
使用 LowLatency 模式时,请考虑下列准则:
使处于低滞后时间模式的时间尽可能短。
在低滞后时间期间,避免分配大量内存。可能会出现内存不足通知,因为垃圾回收所回收的对象较少。
在低滞后时间模式下,最大限度地减少所进行的分配次数,特别是向大型对象堆和固定对象所做的分配。
请注意可能正在进行分配的线程。由于 LatencyMode 属性设置是针对整个进程的,因此也许会在任何可能正在分配的线程上产生 OutOfMemoryException。
将低滞后时间代码包装在受约束的执行区域中(有关更多信息,请参见受约束的执行区域)。
可以通过调用 t(Int32, GCCollectionMode) 方法在低滞后时间期间强制进行第 2
代回收。
Web宿主优化
如果您是通过承载若干个小型网站来共享的服务器的管理员,则可向 .NET Framework 目录下
文件中的 runtime 节点中添加以下 gcTrimCommitOnLowMemory 设置,以优化性能并增加网站容量:
说明:
建议只将此设置用于共享 Web 宿主的情况。
由于垃圾回收器会保留内存以供将来分配,因此其提交空间可能多于真正需要的空间。可以减小此空间,以适应系统内存负载较大的情况。减小此提交空间可以提高性能并扩展容量,以承载更多站点。
启用 gcTrimCommitOnLowMemory 设置后,垃圾回收器将评估系统内存负载,并在负载达到 90% 时进入修整模式。垃圾回收器将一直处于修整模式,直到负载下降到 85% 以下。
当情况允许时,垃圾回收器可以确定 gcTrimCommitOnLowMemory 设置对当前应用程序没有帮助,并将其忽略。
示例
下面的 XML 片段演示如何启用 gcTrimCommitOnLowMemory 设置。椭圆指示将在 runtime 节点中的其他设置。
. . .
. . .
公共语言运行时执行的完整垃圾回收有时可能会对性能产生负面影响。对于处理大量请求的服务器而言,这个问题尤为突出;在这种情况下,长时间的垃圾回收可能会导致请求超时。若要防止在重要时间段发生完整垃圾回收,可以让系统通知您即将发生完整垃圾回收,然后您可以采取措施将工作负荷重定向到其他服务器实例。您也可以在当前服务器实例不需要处理请求时自己引发回收。
说明:
仅当禁用并发垃圾回收时才能使用此功能。默认情况下会启用并发垃圾回收,除非您在宿主环境中运行应用程序且宿主已为您更改了该配置。在使用服务器垃圾回收时,并发垃圾回收不可用。此功能不支持并发垃圾回收,原因是在进行并发垃圾回收期间允许进行内存分配。有关如何禁用并发垃圾回收的信息,请参见
有关垃圾回收的更多信息请参照这里
通过将对象的范围限制为 protected,您可以防止应用程序用户直接调用对象的 Finalize 方法。除此之外,我们强烈建议您不要直接从应用程序代码中调用非基类的类的 Finalize 方法。为适当释放非托管资源,建议您实现公共的 Dispose 或 Close 方法,这两个方法可为对象执行必要的清理代码操作。IDisposable 接口为实现接口的资源类提供 Dispose 方法。因为 Dispose 方法是公共的,所以应用程序用户可以直接调用该方法来释放非托管资源占用的内存。在正确实现 Dispose
方法时,Finalize 方法在未能调用 Dispose 方法的情况下充当防护措施来清理资源。有关正确实现的更多信息,请参见 实现 Dispose 方法。
本节内容
实现 Dispose 方法
描述用于释放非托管资源的 Dispose 方法的实现。
重写 Finalize 方法
描述 Finalize 和 Dispose 方法的合作方式。
C# 中的析构函数语法
描述 Finalize 方法在 C# 中的等效方法。
使用封装资源的对象
描述确保 Dispose 方法得到调用的方式,例如 C# using 语句(在 Visual Basic 中为 Using)。
Dispose 方法
释放对象的模式(称为释放模式)对对象的生存期进行规定。
类型的 Dispose 方法应释放它拥有的所有资源。它还应该通过调用其父类型的 Dispose 方法释放其基类型拥有的所有资源。父类型的 Dispose 方法应该释放它拥有的所有资源,进而调用其父类型的 Dispose 方法,从而在整个基类型层次结构中传播此模式。若要帮助确保始终正确地清理资源,Dispose 方法应该可以多次调用而不引发异常。
对只使用托管资源的类型(如数组)实现 Dispose 方法并不能提高性能,原因是这些类型由垃圾回收器自动回收。应主要对使用本机资源的托管对象和向 .NET Framework 公开的 COM 对象使用
Dispose 方法。使用本机资源的托管对象(如 FileStream 类)实现 IDisposable 接口。
重要说明:
C++ 程序员不应该使用本主题。而应参见 Destructors and Finalizers in Visual C++。在 .NET
Framework 2.0 版中,C++ 编译器为实现资源的确定性处置提供支持,并且不允许直接实现
Dispose 方法。
Dispose 方法应为它要释放的对象调用 SuppressFinalize 方法。如果对象当前在终止队列中,则
SuppressFinalize 会阻止调用其 Finalize 方法。请记住,执行 Finalize 方法会降低性能。如果 Dispose 方法已完成清理对象的工作,垃圾回收器就不必调用对象的 Finalize 方法。
为 ive 方法提供的代码示例演示了强行垃圾回收如何在回收对象的成员仍在执行时引起终结器运行。在较长的 Dispose 方法末尾最好调用 KeepAlive 方法。
参考代码 -
Finalize 方法
Finalize 方法在未能调用 Dispose 方法的情况下充当防护措施来清理资源。您应该只实现
Finalize 方法来清理非托管资源。您不应该对托管对象实现 Finalize 方法,因为垃圾回收器会自动清理托管资源。默认情况下,ze 方法不进行任何操作。如果要让垃圾回收器在回收对象的内存之前对对象执行清理操作,您必须在类中重写此方法。
说明:
您无法在 C# 或 C++ 编程语言中重写 Finalize 方法。在 C# 中可使用析构函数语法实现
Finalize 方法。在 .NET Framework 2.0 版中,C++ 为实现 Finalize 方法提供了自己的语法,如 Destructors and Finalizers in Visual C++ 中所述。在早期版本中,C++ 与 C# 一样也使用析构函数语法来实现 Finalize 方法。
ze 方法的范围是受保护的。当在类中重写该方法时,您应该保持这个有限的范围。通过保护 Finalize 方法,您可以防止应用程序的用户直接调用对象的 Finalize 方法。
对象的 Finalize 方法应该释放该对象保留的所有资源。它还应该调用该对象基类的 Finalize 方法。对象的 Finalize 方法不应对任何非其基类的对象调用方法。这是因为被调用的其他对象可能和调用对象在同一时间被回收,例如公共语言运行库关闭这种情况。
如果您允许任何异常避开 Finalize 方法,系统将认为方法返回,并继续调用其他对象的 Finalize
方法。
中的析构函数语法
您无法从 C# 或 C++ 编程语言中调用或重写 ze 方法。C# 将析构函数用作编写终止代码的机制,如 析构函数(C# 编程指南) 中所述。
在 C++ 中,析构函数语法用于实现 Dispose 方法。C++ 为实现 Finalize 方法提供了自己的语法,如 Destructors and Finalizers in Visual C++ 中所述。
说明:
在 .NET Framework 2.0 版之前,C++ 与 C# 一样也使用析构函数用法来实现 Finalize 方法,当时没有特殊语法来实现 Dispose 方法。若要编译使用早期语法的 C++ 代码,请使用
/clr:oldSyntax 编译器选项。
尽管看起来相似,但 C# 和 C++ 析构函数的语义与未托管 C++ 析构函数的语义并不相同。托管代码不支持任何与 C++ 析构函数语义相似的语义。
资源的对象
如果您要编写代码,而该代码使用一个封装资源的对象,您应该确保在使用完该对象时调用该对象的 Dispose 方法。要做到这一点,可以使用 C# 的 using 语句,或使用其他面向公共语言运行库的语言来实现 try/finally 块。
C# 的 Using 语句
C# 编程语言的 using 语句通过简化必须编写以便创建和清理对象的代码,使得对 Dispose 方法的调用更加自动化。using 语句获得一个或多个资源,执行您指定的语句,然后处置对象。请注意,using 语句只适用于这样的对象:这些对象的生存期不超过在其中构建这些对象的方法。下面的代码示例将创建并清理 ResourceWrapper 类的实例,如 C# 示例实现 Dispose 方法中所示。
Class myApp
{
public static void Main()
{
using (ResourceWrapper r1 = new ResourceWrapper())
{
// Do something with the object.
thing();
}
}
}
以上合并了 using 语句的代码与下面的代码等效。
Class myApp
{
public static void Main()
{
ResourceWrapper r1 = new ResourceWrapper();
try
{
// Do something with the object.
thing();
}
finally
{
// Check for a null resource.
if (r1 != null)
// Call the object's Dispose method.
e();
}
}
}
使用 C# 的 using 语句,可以在单个语句(该语句在内部同嵌套的 using 语句是等效的)中获取多个资源。有关更多信息及代码示例,请参见 using 语句(C# 参考)。
Try/Finally 块
当您用 C# 以外的语言编写托管代码时,如果该代码使用一个封装资源的对象,请使用
try/finally 块来确保调用该对象的 Dispose 方法。下面的代码示例将创建并清理 Resource 类的实例,如 Visual Basic 示例实现 Dispose 方法中所示。
Class myApp
Public Shared Sub Main()
Resource(r1 = New Resource())
Try
' Do something with the object.
thing()
Finally
' Check for a null resource.
If Not (r1 Is Nothing) Then
' Call the object's Dispose method.
e()
End If
End Try
End Sub
End Class
C#中的析构函数语法
您无法从 C# 或 C++ 编程语言中调用或重写 ze 方法。C# 将析构函数用作编写终止代码的机制,如 析构函数(C# 编程指南) 中所述。
【参考】析构函数(C# 编程指南)
析构函数用于析构类的实例。
备注
不能在结构中定义析构函数。只能对类使用析构函数。
一个类只能有一个析构函数。
无法继承或重载析构函数。
无法调用析构函数。它们是被自动调用的。
析构函数既没有修饰符,也没有参数。
例如,下面是类 Car 的析构函数的声明:
【C#】
Class Car
{
~Car() // destructor
{
//
}
}
该析构函数隐式地对对象的基类调用 Finalize。这样,前面的析构函数代码被隐式地转换为以下代码:
【C#】
protected override void Finalize()
{
try
{
//
}
finally
{
ze();
}
}
这意味着对继承链中的所有实例递归地调用 Finalize 方法(从派生程度最大的到派生程度最小的)。
说明:
不应使用空析构函数。如果类包含析构函数,Finalize 队列中则会创建一个项。调用析构函数时,将调用垃圾回收器来处理该队列。如果析构函数为空,只会导致不必要的性能损失。
程序员无法控制何时调用析构函数,因为这是由垃圾回收器决定的。垃圾回收器检查是否存在应用程序不再使用的对象。如果垃圾回收器认为某个对象符合析构,则调用析构函数(如果有)并回收用来存储此对象的内存。程序退出时也会调用析构函数。
可以通过调用 Collect 强制进行垃圾回收,但大多数情况下应避免这样做,因为这样会导致性能问题。
使用析构函数释放资源
通常,与运行时不进行垃圾回收的开发语言相比,C# 无需太多的内存管理。这是因为 .NET
垃圾回收器会隐式地管理对象的内存分配和释放。但是,当应用程序封装窗口、文件和网络连接这类非托管资源时,应当使用析构函数释放这些资源。当对象符合析构时,垃圾回收器将运行对象的 Finalize 方法。
资源的显式释放
如果您的应用程序在使用昂贵的外部资源,我们还建议您提供一种在垃圾回收器释放对象前显式地释放资源的方式。可通过实现来自 IDisposable 接口的 Dispose 方法来完成这一点,该方法为对象执行必要的清理。这样可大大提高应用程序的性能。即使有这种对资源的显式控制,析构函数也是一种保护措施,可用来在对 Dispose 方法的调用失败时清理资源。
有关清理资源的更多详细信息,请参见下列主题:
清理非托管资源
实现 Dispose 方法
using 语句(C# 参考)
示例
下面的示例创建三个类,这三个类构成了一个继承链。类 First 是基类,Second 是从 First 派生的,而 Third 是从 Second 派生的。这三个类都有析构函数。在 Main() 中,创建了派生程度最大的类的实例。注意:程序运行时,这三个类的析构函数将自动被调用,并且是按照从派生程度最大的到派生程度最小的次序调用。
【C#】
Class First
{
~First()
{
ine("First's destructor is called.");
}
}
class Second : First
{
~Second()
{
ine("Second's destructor is called.");
}
}
class Third : Second
{
~Third()
{
ine("Third's destructor is called.");
}
}
下面继续
【C#】
Class TestDestructors
{
static void Main()
{
Third t = new Third();
}
}
/* Output (to VS Output Window):
Third's destructor is called.
Second's destructor is called.
First's destructor is called.
*/
【参考】WeakReference 类
表示弱引用,即在引用对象的同时仍然允许垃圾回收来回收该对象。
备注
弱引用允许垃圾回收器在回收对象的同时仍然允许应用程序访问该对象。如果需要该对象,则仍然可以获取对该对象的强引用,并阻止对它进行回收。有关如何使用短弱引用和长弱引用的更多信息,请参见弱引用。
参考代码 -
【参考】垃圾回收内部原理
.NET中的垃圾回收器(GC)完全彻底帮助开发人员从追踪内存的使用和确定什么时候释放内中解脱出来。
Microsoft® .NET CLR (公共语言运行库)要求所有资源都从托管堆中进行分配。你无需释放托管堆中的对象——当应用程序不再需要这些对象时,对象将被自动释放。
内存不是无限的。垃圾回收器需要执行回收以便释放内存。垃圾回收器的优化引擎会对已做的分配选择最好的回收时间(准确标准由微软提供)。当垃圾回收器执行回收时,它先找出托管堆中不再被应用程序使用的对象然后执行相应操作收回这些对象的内存空间。
然而,为了进行自动内存管理,GC必须知道根的位子。也就是说,它应该知道一个对象什么时候不再被应用程序使用了。在.NET中,GC是通过一个称之为元数据的东西了解到这些的。.NET中使用的每种数据类型都通过元数据来描述它。在元数据的帮助下,CLR知道内存中每个对象的布局,在垃圾回收的整理阶段给GC提供帮助。没有这些信息,GC将不会知道一个对象在哪儿结束和下一个从哪儿开始。
垃圾回收算法
应用程序根(Application Roots)
每个应用程序都有一套根(Roots)。根标识存储位置,这个位置或者指向一个托管堆的对象,或者指向一个空对象(null)。
比如:
一个应用程序中所有全局和静态对象指针。
一个线程堆栈中所有局部变量/参数对象指针。
托管堆中所有CPU登记的对象指针。
FReachable队列中的对象指针。
活动根的表由JIT编译器和CLR维护,并且对于垃圾回收器的算法是访问。
实现
.NET中的垃圾回收是用跟踪回收实现的,确切的说CLR实现了标记(Mark)/整理(Copact)回收器。
这个方法有以下两个阶段组成:
阶段I:标记(Mark)
找到可以被收回的内存。
当GC开始运行时,它假设堆中的所有对象都是垃圾。换句话说,它假设应用程序的根没有指向堆中的的任何对象。
阶段I中包含下列步骤:
① GC识别存活对象的引用或应用程序根。
② 从根开始遍历,建一张可以从根遍历的所有对象的图。
③ 如果GC准备尝试添加一个已经在图中的对象,它就停止这条路径的遍历。这样做有两个目的,第一个是极大的优化性能,因为它不会遍历一套对象一次以上。第二是防止当有对象的循环连接列表时而发生死循环,因此循环被有效的控制了。
一旦所有的根都被检查完后,垃圾回收器的图中包含了所有可以从应用程序根遍历到的对象。任何不再图中的对象都不能被应用程序访问到,也就是所谓的垃圾。
阶段II包含下列步骤:
① 现在GC线性地遍历堆,寻找邻近的垃圾对象块(现在被认为是空闲空间)。
② 然后GC往下移动内存中的非垃圾对象,去掉堆中的所有空隙。
③ 移动内存中的对象导致对象指针失效。因此GC需要修改应用程序的根使对象的指针指向新的位置。
④ 另外,如果对象包含一个指向其它对象的指针,GC也会负责纠正这些指针。
在所有垃圾被标识完以后,所有的非垃圾对象也被整理,并且所有非垃圾对象的指针也被修正,最后一个非垃圾对象后的指针指向下一个被添加对象的位置。
阶段II:整理(Compact)
把所有存活的对象移到堆的末端,空出堆顶端的空间。
终结(Finalization)
.NET Framework的垃圾回收器能暗中追踪由应用程序创建的对象的生命周期,但是当它遇到对象包装了非托管资源(比如文件、窗口或网络连接等)时却无能为力。
一旦应用程序不再使用那些非托管资源时需要显示地释放它们。.NET Framework为对象提供了终结(Finalize)方法:在垃圾回收器收回这个对象的内存时,必须执行对象的这个方法来清除它的非托管资源。由于缺省的Finalize方法什么都没做,如果需要显示清除资源必须覆盖这个方法。
如果一把Finalize方法当作只是C++中析构函数另外一个名字那也不足为怪。虽然它们都被赋予了释放对象占有的资源的任务,但是它们还是有很不相同的语义。在C++中,当对象推出作用域时析构函数会立刻被调用,而Finalize方法是在起动垃圾回收清除对象时才被调用的。
.NET中,由于终结器(Finalizer)的存在使得垃圾回收的工作变得更加复杂了,因为它在释放对象前增加了许多额外的操作。
无论什么时候,在堆上分配一个含有Finalize方法的新对象时,都会有一个指向这个对象的指针被添加到一个称为Finalization队列的内部数据结构当中。当对象不能再次被遍历到时,GC就认为这个对象是垃圾。GC首先扫描Finalization队列查找这些对象的指针,当指针被找到时,把它从Finalization队列中去掉并添加到另外一个名为FReachable队列的内部数据结构中,使这个对象不
这时,GC完成了确定垃圾。然后整理(Compact)可收回的内存,由专门的线程负责清空FReachable队列并执行对象的Finalize方法。
第二次垃圾回收器被触发的时候,它把被终结(Finalize)的对象看作真正的垃圾,然后简单的释放它们的内存。
由此可知当一个对象需要终结时,它先死,然后存活(复活),然后再次并且最终地死去。推荐避免使用Finalize方法,除非有需要。Finalize方法会增加内存压力,因为直到两次垃圾回收被启动时,对象占用的内存和资源才会得到释放。因此你无法控制两次Finalize方法执行的顺序,它可能会导致无法预料的后果。


发布评论