有时,本地资源会消耗大量的内存,但是用于包装该资源的托管对象只占用了非常少的内存。一个典型的例子就是位图。一个位图可能占用几兆字节的本地内存,但是托管对象却极小,因为它只包含了一个hbitmap(一个4或8字节的值),从CLR角度看,一个进程可以在执行一次垃圾回收之前分配数百个位图(他们用的托管内存太少了)。但是,如果进程操作许多位图,进程的内存消耗将以一个恐怖的速度增长。为了修正这个问题,GC类提供了以下两个静态方法:
public static void AddMemoryPressure(long bytesAllocated);
public static void RemoveMemoryPressure(long bytesAllocated);
如果一个类要包装可能很大的本地资源,就应该使用这些方法提示垃圾回收器实际需要消耗多少内存。垃圾回收器内部会监视内存压力,压力变大时,就会强制执行垃圾回收。
有的本地资源的数量是固定的。例如:Window以前就限制它只能创建5个设备上下文。应用程序能打开的文件数量也必须有限制。同样的,从CLR的角度看,一个进程可以在执行垃圾回收之前分配数百个对象(每个对象都使用了极少的内存)。但是,如果这些本地资源的数量有限,那么一旦超过允许数量的资源,通常会导致抛出异常。为了解决这个异常,.NET提供了如下类:
// 摘要:
// 跟踪未处理的句柄,并在达到指定阈值时强制执行垃圾回收。
public sealed class HandleCollector
{
// 摘要:
// 使用一个名称以及一个阈值(在达到该值时开始执行句柄回收)初始化 System.Runtime.InteropServices.HandleCollector
// 类的新实例。
//
// 参数:
// name:
// 回收器的名称。此参数允许您为跟踪句柄类型的回收器分别命名。
//
// initialThreshold:
// 指定何时开始执行回收的值。
//
// 异常:
// System.ArgumentOutOfRangeException:
// initialThreshold 参数小于 0。
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public HandleCollector(string name, int initialThreshold);
//
// 摘要:
// 使用一个名称、一个指定何时开始执行句柄回收的阈值,以及一个指定必须进行句柄回收的阈值初始化 System.Runtime.InteropServices.HandleCollector
// 类的新实例。
//
// 参数:
// name:
// 回收器的名称。此参数允许您为跟踪句柄类型的回收器分别命名。
//
// initialThreshold:
// 指定何时开始执行回收的值。
//
// maximumThreshold:
// 指定必须开始进行回收的值。此值应设置为可用句柄的最大数量。
//
// 异常:
// System.ArgumentOutOfRangeException:
// initialThreshold 参数小于 0。- 或 -maximumThreshold 参数小于 0。
//
// System.ArgumentException:
// maximumThreshold 参数小于 initialThreshold 参数。
public HandleCollector(string name, int initialThreshold, int maximumThreshold);
// 摘要:
// 获取回收的句柄的数量。
//
// 返回结果:
// 回收的句柄的数量。
public int Count { get; }
//
// 摘要:
// 获取一个值,该值指定了何时开始执行回收。
//
// 返回结果:
// 指定何时开始执行回收的值。
public int InitialThreshold { get; }
//
// 摘要:
// 获取指定必须开始进行回收的值。
//
// 返回结果:
// 指定必须开始进行回收的值。
public int MaximumThreshold { get; }
//
// 摘要:
// 获取 System.Runtime.InteropServices.HandleCollector 对象的名称。
//
// 返回结果:
// 此 System.Runtime.InteropServices.HandleCollector.Name 属性允许您为跟踪句柄类型的回收器分别命名。
public string Name { get; }
// 摘要:
// 增加当前句柄计数。
//
// 异常:
// System.InvalidOperationException:
// System.Runtime.InteropServices.HandleCollector.Count 属性小于 0。
public void Add();
//
// 摘要:
// 减少当前句柄计数。
//
// 异常:
// System.InvalidOperationException:
// System.Runtime.InteropServices.HandleCollector.Count 属性小于 0。
public void Remove();
}
如果一个类包装数量有限的本地资源,就应该使用这个类的一个实例来提示垃圾回收器它实际需要消耗资源的多少个实例。该类的对象会在内部监视这个计数,当计数变大时,就强制执行垃圾回收。
注意:在内部,GC. AddMemoryPressure和HandleCollector.Add方法会调用GC.Collect方法,强迫垃圾回收再第0代达到预算前启动。一般都强烈反对强制开启一次垃圾回收,因为它对应用程序的性能造成负面影响。但是,类之所以调用这个方法,目的是保证应用程序能用到有限的本地资源。本地资源消耗用光了,应用程序就会失败。对于大多数应用程序,性能受到一些损失总好过与完全无法运行。