.NET如何使用内存---餐馆案例分析[转载]
前几天在工作时,我听到了我的一个同事正给一位客户解释有关内存的使用和为什么会出现内存异常。虽然我在早期的一篇文章中谈到了内存溢出和内存管理,但我觉得他的分析实在太有趣了,因此我想将此分享给大家。
声明: 为了避免冗长,我简化许多东西。譬如,我是这么说的,垃圾回收器分配64MB区段空间。尽管在不同的framework版本下分配的区段空间大小是不同的。并且跟最初给各个对象分配的空间有关,譬如,读取大对象堆。另外的一些有关内存分配细节依赖于配置,我在这将这些细节都排除了。
第一部分 -通常的内存使用
假如你读过我先前的贴子,你会知道:32位系统进程典型寻址空间为2GB的虚拟地址空间。这是供工作的内存空间,它独立于同时占有多少RAM空间。占有更多的RAM有益于提升性能,但它并不会帮助扩展到2GB之外的地址空间。
此时,你将这2GB的地址空间想象成餐馆的所有占地空间。
你给对象(不管受否是.net对象)分配空间时,通常两步走:先预定空间,然后将对象置于预定的空间内。
内存预定工作如同在餐馆中预定餐桌。正如餐馆一样,内存预定依赖其内存管理器(memory manager),他会帮你预定到一块连续的内存(memory in chunks)。譬如,你有一个3人的聚会,餐馆里很可能没有3人餐桌,但你可能会预定一个4人餐桌,你使用其中的3个座位,浪费一个座位。
相对于内存空间,你预定餐桌好比预定内存(占用虚拟字节),实际占用的座位(3个座位)好比实际占用内存(占用私有字节)。没有预定的地板空间就是剩余可用内存。
在一个美好的晚上,餐馆的情景可能这样:
蓝色区域表示已经预订了的空间,红色表示实际使用空间,白色表示剩余可用空间。 如下所示:
现在假如某人某人电话预订一个3人餐桌,他将被告之餐馆已无可用餐桌。因为3个人坐在一起需要一个4人餐桌。尽管可以分在两个2人餐桌上坐下,但这似乎并不是一个好主意,因为你们想坐在一起。
相似地,内存预定也不可能将内存分成小块,分散在各个不同的地方。要不就是什么也不分配,要不就是在一块连续的区域内分配。因此内存预定也如上面的预定3人餐桌情景,那么将会“内存溢出”,尽管还有足够的小块剩余空间可用。
细心的观众也许会说,不是可以将各个餐桌靠陇,使他们一个连着一个,这不是腾出一个4人餐桌出来了吗?但是已经用餐的客户愿意此时移动餐桌吗?预定内存正如这样。
我们谈论了内存碎片,也谈了剩余但不可用的内存块(因为他们太小了,不足以容纳一个新的餐桌),并且我们也谈谈有多少已经预订了但尚未使用的内存(这不同于占用虚拟字节和私有字节的内存)。
第二部分 -.NET的垃圾回收
绝大多数情况下,你依靠某种内存管理器(memery manager)(譬如,NT Heap, C++ Heap,GC等等)在应用程序中创建对象。在餐馆案例情景下,你会将内存管理器比作女服务员,她会为你预定座位并会带你到你的座位上。例如,调用了malloc方法,就不需要提供欲分配到的地址,而只需告之需要的内存区块大小,malloc会返回内存预定的地址。ok,在“C++ heap"区的table 1就是安置给你的地址。
.NET的垃圾回收机制(GC) 要更进一步。进程中,只要想使用.net对象,它会预先保留一张大“桌子”(64座的桌子)。当某人创建了.net对象,GC会接待他们到桌子的下一个可用座位。有时接待员也会在餐桌旁来回走动,检查是否有人已经用餐完毕并邀请他们离开,此时其他的客人会就坐该餐桌。一些人可能需要等待其他人用餐完毕才能离开(比如引用对象),因此他们必须呆着等待。也有一些"大爷"人物,占据着靠窗的座位,很生气地说,“我不走了”。这就意味着,他不能腾出位置来,其他人也过不来。
客人和客人间留着的空位好比.NET内存碎片。
一旦64座的桌子坐满了,如需接待新来客人,GC需要预定一个新的64座的桌子。如果预定失败,就会抛出一个内存溢出异常。
但是,它的真正面目怎样?
ok, 分析得足够多了,这你将看到真正的asp.net应用程序内存情况.
再一次,红色的部分是已使用内存,篮色部分是预定但尚未使用内存,白色是剩余可用内存。
在内存空间的末端,你看到的一些小点很可能是一些dll。如同餐馆情形下,尽管还有很多的白色空间,很可能在两个红点间的空隙不足以容纳一个64 M的区块,这样,在下一次GC申请一块新的内存时会得到一个内存溢出异常。
这些小红点(dll)像这样布满整个空间的原因是由于这些特殊的dll装载在更适合的基地址上。你不能对那类dll多做些什么,因为很难预判他们将分配的更适合的"好"的地址在哪,但对此你做的是,能找出实际使用的内存到底去哪了。