你有32位系统?你在写32位应用?你可能对如何增加你的应用的内存,减少硬盘存储大小更加感兴趣!在应用领域秘诀是什么?
在这篇文章我只谈虚拟内存。真实内存和虚拟内存的关系不再这篇讨论之列,它们将在其它的文章讨论。
当你的应用在公共语言运行环境(CLR)上运行时,计算机系统将为其分配一个默认的内存空间啊。CLR 自动加载mscorlib.ddl、依赖、其它组件。因此,虽然有4G内存,但是应用只需要很小的一块用来工作,通常2到3G用来虚拟存储。
你的项目可能需要创建额外的域名,我将引导你如何为你的应用创建额外域名啊。在32位系统上,当你的项目加载了mscrolib.ddl、依赖、其它组件后,你将有1G到2G的内存空间来创建你的额外域名。你可能有一个第二域名和多个第二域名去创建其它的域名。
大多数文章和书籍都会谈到通过使用第二个程序域来创建一个独立环境供非新人应用运行使用。而我想要来存储数据。使用第二个程序域来存储大数据集和数据缓存是很棒的。
NET CLR 会创建默认的程序域,但当你一定要创建任意的程序域。用于存储和访问次级程序域中数据的代码必须编写出来。我将会告诉你如何创建和销毁次级域以及如何增加可执行代码到次级域。(不过,)编程时你必须要考虑如下限制。
每个AppDomain有其自有的寻址空间。各程序域间的内存是独立不可见的。程序域间数据必须被明确地传输。
域间传输的数据必须是可序列化的。庆幸的是,许多标准集合已经被指定为可序列化。(但)你必须使用[Serializable]属性给你自己的类和结构体注明。
迭代器和枚举器不是可序列化的。ForEach语句是无法跨域实现的。可选方案就是数组下标访问或者通过子集来实现(迭代和枚举)。 保存/获取子集的例子:"AddRange(elementList);" 和"List<string> elements = GetRange(from, to);" 。
构造函数是不能带参数的。你必须使用属性和函数来给对象传递初始化参数。(这就意味着)你需要通过另外修改的函数来保证同等于原来的构造函数所实现的初始化结果。
1. 通常来说,在其传统的权衡之中是通过使用更多的内存来换取更快的执行速度。在此种情况下,对于时间权衡中的记忆并不是真实的;序列化与反序列化数据需要时间。该应用程序运行速度比是否存贮在作为处理所述数据的代码的相同域名中的数据更慢。然而,这有可能对于数据的处理转移到这将消除大部分的开销成本的二级域名。
2. 异常处理,必须精心策划。异常抛出的二级域名必须在二级域名中被处理。如果没有处理,默认域通过 UnhandledExceptionEventHandler 事件接收异常的通知与终止的应用程序。
示例程序使用泛型字符串集合来展示跨域对象。示例程序创建和处理在两者主域名内存或者在一个二级域名之中的字符串集合。泛型集合被命名为DomainList<T>.DomainList<T>是一个围绕泛型List<T>类的包装。示例程序创建一组被转化为固定长度字符串的随机数。举个例子,4567这个数字被转换为右对齐,填充空白的文字,“4567”。文字字符串被存储在一个DomainList<T>类的实例之中。通常来说,这将被编码为:
1. DomainList<string> storedText = new DomainList<string>(); 2. storedText.Add(rightJustifiedNumber); 3. storedText.Dispose();
语句1创建一个专门针对字符串数据的Domainlist对象的实例。
语句2增加了一个字符串对象。字符串将被存储在当前的域名的内存之中。
语句3处理storeText对象并释放与对象相关联的内存。
为了在不同的域内存中存储数据,你必须创建一个应用程序域,并且在其中实例化泛型对象DomainList<T>。就像下面这段代码就可最简单地实现。
1. AppDomain storageDomain = AppDomain.CreateDomain("SecondaryDomain"); 2. DomainList<string> storedText = (DomainList<string>)storageDomain.CreateInstanceAndUnwrap( typeof(DomainList<string>).Assembly.FullName, typeof(DomainList<string>).FullName); 3. storedText.Add(rightJustifiedNumber); 4. storedText.Dispose(); 5. AppDomain.Unload(storageDomain);
语句1 创建了一个新域并制定了一个名称,域名称应该是友好展示的(难道有人起很黄很暴力的名字么-_->)。
语句2 在当前域与次级域创建泛型对象 theDomainList<T>。同时也创建了允许你跨域传输数据的跨域环境。这样子后会在次级域中创建不止一个对象。
语句3 给对象添加了一个字串。该字串将被存储在次级域内存中。
语句4 销毁了storedText 对象并释放了与该对象相关的内存。AppDomain 的内存必须分开销毁。
语句5 用于销毁次级域。你可能在次级域中创建了多个跨域对象。所以必须在次级域销毁之前把这么多的跨域对象销毁。
DomainList<T> 对象是关于泛型集List<T>的包装. List<T>类提供了用于操作集合中数据的函数。DomainList<T>提供跨域通信所需信息和函数并且功能上有所增强。这样的包转器也是有很多理论要求的。
不可继承.DomainList<T>已经从MarshalByRefObjector等价继承. C#不支持多次继承,所以我通过在DomainList<T>对象里创建List<T>对象来包装List<T>。 之后又写了我所需要的增强函数。最终用户程序将调用DomainList<T>的增强函数,该函数间接调用了与List<T>对应函数的等价函数来完成工。
数据必须可序列化。枚举器和迭代器不可序列化。你可能会想提供类似于下标或者数据取回的替代方法来实现迭代。 我创建了Add(mySublist),Remove(startIndex, endIndex)以及Retrieve(startIndex, endIndex)函数来加速大数据交换。Sort()在次级域中运行,因此会消耗大量的开销来执行序列化/反序列化。
你不能跨域使用throw/catch逻辑来处理异常。你得通过其他技术传输异常情况。增强的泛型DomainList<T>中的函数里直接使用try/catch逻辑捕捉List<T>抛出的异常。该异常将通过DomainList<T>的LastException属性在后台传给最终用户。DomainList<T>通常将使用Source属性创建一个新的异常以指明当异常抛出时所执行的函数。新的异常对象的InnerException属性就会返回一个原始错误异常。如果访问不了,会产生错误的返回。比如, x.Count 将返回 -1 当List<T>中元素数无法被确定时。缺点是很明显的;调用者必须检查LastException属性否则异常将会消失。
LastException 属性在单线程类中发挥得好,多线程类就不一样了。你很可能会在一个线程中错过一个错误,或者收到一个错误通知单该线程并不是错误的始作俑者。 下标访问引起的返回错误是有问题的。
编写DomainList<T>类与编写一个普通的类非常类似。直接就是这样声明的:
[Serializable] public class DomainList<T> : MarshalByRefObject, IDisposable
必须指定可序列化属性。
MarshalByRefObject 是跨域通信的对象使用的基类,其通过代理实现。域内对象可直接通信,无需代理。
非常推荐 IDisposable 接口,这样可以尽快地释放跨域资源。
从MarshalByRefObject继承的对象销毁时将会在跨域环境中引起内存泄露。程序域必须被free来释放所有的资源。你应该从CrossAppDomainObject类继承,而不是MarshalByRefObject。 Nathan B. Evans编写了CrossAppDomainObject类并将其公开在 http://nbevans.wordpress.com/2011/04/17/memory-leaks-with-an-infinite-lifetime-instance-of-marshalbyrefobject/
他的解释比我说的强多了,但我还是要指出在编程中的不同之处。 更改public class DomainList<T> : MarshalByRefObject, IDisposable 为public class DomainList<T> : CrossAppDomainObject, IDisposable。IDisiposableis是可选的,当继承自CrossAppDomainObject ,因为CrossAppDomainObject已经包含了dispose的代码。如果你在类中有对象需要销毁时,你必须复写CrossAppDomainObject中的Dispose方法。可以在给出的例程中看DomainListCD<T>对象的完整编程例子。
我已经提供了三个例程。我在x86平台上编译了所有例子。例子使用了32位指针寻址,与系统的模式无关。这对于第三个例子是很重要的。所有的例子必须在64位模式下运行,但是是否有内存限制还不容易确定。
DomainMemoryDemo 和 DomainMemoryDemoCrossAppDomain 是同样的例子,除了DomainList<T> 是继承自 MarshalByRefObject而DomainListCD<T>继承自CrossAppDomainObject。你可以在默认域或者次级域中运行对象测试。选项框"Test the object" checkbox将针对生成的数据执行多项操作以正式编程实现细节(可用)。 "Pre-allocate memory"(预分配内存)选项框将在使用/填充之前给集合提供一个容量。我加这样的选项框仅仅是为了看看这样会对加载时间引起多大的变化。
所生成的报告将显示各类操作经过的时间和CPU时间。例如,CPU的排序时间很重要,它将显示所存储数据的域名之中。你将也看到在数据加载时间里的主要差别;经过3.22秒的默认域与二级域名为16.27相对应。这是使用一个二级域名的开销。
第三个示例项目是DomainMemoryLeakTest.该测试将创建与处置DomainMemory对象,直到内存耗尽和停止测试。使用“一次创建域”运行"正常的功能”应该在一分钟内左右的32位系统之上会失败。所有其他组合将继续执行,直到你按下“停止”按钮。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务