接下来,我将讨论一些用于有效管理内存的设计和实现技术。程序集需要的内存主要取决于该程序集所做的工作,但是程序
集实际使用的内存受到应用程序从事其各种任务的方式的影响。在设计和实现应用程序时,这是一个需要记住的重要特点。
我将分析单元素、内存池和数据流的概念。
返回页首
单元素单元素
应用程序的工作集是 RAM 中当前可用的内存页的集合。初始工作集是应用程序在启动期间消耗的内存页。在应用程序启动
期间执行的任务和分配的内存越多,应用程序准备的时间就越长,初始工作集就越大。这对于桌面应用程序而言尤其重要,
因为用户通常盯着启动画面以等待应用程序进行准备。
单元素模式可以用来尽可能久地延迟对象的初始化。以下代码显示了在 C# 中实现该模式的一个方式。静态字段存放单元素
实例,该实例由 GetInstance 方法返回。静态构造函数(它由 C# 编译器隐式生成,以执行所有静态字段初始值设定项)被
保证在第一次访问该类的成员之前执行并且初始化静态实例,如以下代码所示:
public class Singleton
{
private static Singleton _instance = new Singleton();
public static Singleton GetInstance()
{
return _instance;
}
}
单元素模式可以确保应用程序通常只使用类的单个实例,但是仍然允许根据需要创建备用实例。这可以节约内存,因为应用
程序可以使用一个共享实例,而不是让不同的组件分配它们自己的私有实例。使用静态构造函数可以确保在应用程序的某个
部分需要该共享实例之前,不会为该实例分配内存。这在支持很多不同类型功能的大型应用程序中可能很重要,因为只有在
实际使用该类时,才会分配对象的内存。
该模式和类似的技术有时称为“惰性初始化”,原因是在实际需要之前不执行初始化。在很多情况下,当初始化可以作为针对对
象的第一个请求的一部分发生时,惰性初始化很有用。在静态方法足可以满足需要的情况下,不应当使用它。换句话说,如
果您要创建单元素以便访问该单元素类的一批实例成员,则请考虑通过静态成员公开相同的功能是否更合理,因为那样将不
需要实例化该单元素。
返回页首
池机制池机制
一旦应用程序启动并运行,内存利用就将受到系统需要的对象的数量和大小的影响。对象池机制可以降低应用程序所需的分
配的数量,从而降低应用程序所需的垃圾回收的数量。池机制相当简单:对象被重新使用,而不是让它被垃圾回收器回收。
对象存储在某种类型的列表或数组(称为“池”)中,并且根据请求分发给客户端。如果对象的实例被反复使用,或者如果对
象的构建具有开销较大的初始化方面,以至于重新使用现有实例要比处置一个现有实例并且从头创建一个全新的实例更好一
些,则该机制尤其有用。
让我们考虑一个可以有效使用对象池的方案。假设您要为一家大型保险公司编写一个系统,以便将患者信息存档。医生在白
天收集信息,并且在每个晚上将信息传输到中心位置。代码可能包含完成如下工作的循环:
while (IsRecordAvailable())
{
PatientRecord record = GetNextRecord();
... // process record
}
在该循环中,一个新的 PatientRecord 在每次执行循环时返回。GetNextRecord 方法的最显而易见的实现是在每次调用时创
建一个新对象,从而需要分配和初始化该对象,最终对该对象进行垃圾回收,以及终结该对象(如果该对象具有完成器的
话)。在使用对象池时,分配、初始化、回收和终结只发生一次,从而减少了内存使用和所需的处理时间。
在某些情况下,可以重新编写代码,以便用如下代码利用该类型上的 Clear 方法:
PatientRecord record = new PatientRecord();
while (IsRecordAvailable())
{
record.Clear();
FillNextRecord(record);
... // process record
}
在该代码片段中,创建了单个 PatientRecord 对象,并且 Clear 方法重置了内容以便可以在循环内部重新使用它。
FillNextRecord 方法使用现有对象,从而避免了重复分配。当然,该代码片段每次执行时,您仍然要付出分配、初始化和回
收一次的代价(尽管如此,这仍然要比每次执行循环时付出相应的代价更好一些)。如果初始化的代价高昂,或者从多个线
程中同时调用该代码,则这一重复创建的影响仍然可能成为问题。