1. commit limit与current commit charge
接上文,我们看到testlimit -r开关,只是预留虚拟内存,并没有实际进行提交(commit)。预留虚存并不存储数据或代码,但有时候应用需要这个预留(就像预订坐位一样),用以创建大块虚存,并且在需要的时候进行提交,以确保提交的内存在地址空间上是连续的。当进程提交一块虚存时,操作系统要确保存储在内存里的数据要么全部在内存里,要么全部在硬盘中。这意味着进程的运行有另外一个限制:commit limit。
commit limit是物理内存和页文件大小的总和。事实上,并不是所有物理内存被计算在内,因为操作系统预留了一部分给自己使用。所有存活进程提交的虚存的总和,称之为current commit charge,它不能超过系统的commit limit。当commit limit值达到时,虚存的分配就会失败。这意味着标准的32位进程,在达到2GB地址空间限制之前,有可能会出现虚存分配失败。
current commit charge和commit limit由进程管理器来跟踪,其数据显示如下图:
在Vista和Win2008之前的版本,可能显示的不叫current commit charge和limit,而采用的是current commit change ("PF Usage"),实际上就是page file usage:
在Vista和win2008下,任务管理器不显示commit charge图,只给出current commit charge和limit值,表示为"Page File"
你可以使用Testlimit -m 来对commit limit进行压力测试,让它强行分配已提交内存。32位版本的Testlimit在达到commit limit之前可能不会碰到地址空间的级限,这要依赖于物理内存的大小、虚存页文件的大小以及current commit charge值的大小。如果在32位windows下运行,并且要看看在达到commit limit时系统的行为,可以多运行几个Testlimit实例,直到有一个在用尽地址空间之前,达到commit limit。
注意,缺省情况下,页文件是动态增长的,那意味着commit limit也会动态增长(在commit charge变大并且接近它时),并且,即使页文件达到它的最大值,系统也会放掉部分内存,进行内部调节,就如同普通的应用程序缓存完数据以后,会放掉一部分内存一样。Testlimit在达到commit limit之后,会sleep几秒钟,接着尝试分配更多内存,不断重复,直至被强行终断。
如果你运行的是64位的Testlimit, 几乎可以肯定的是,在用尽地址空间之前,它就会达到commit limit,除非物理内存和页文件大小加起来能超过8TB,这个8TB在第一节里,已经描述了,它是64位应用可以访问的地址空间。这里是64位Testlimit在8GB内存的系统上(指定每次分配100M)的运行过程中的部分结果:
这是当Testlimit停顿下来的提交历史图:
当系统虚存接近耗尽时,应用程序可能会失败,你有可能会看到奇怪的错误消息,在大多情况下,Windows将会提示内存不够。如下图:
当你退出Testlimit时,commit limit又会降下一点点,因为内存管理器会把虚存页文件进行截短,以适应Testlimit的极端的内存提交请求。这里,进程管理器显示当前的limit正好在峰值以下。
2. 进程提交的内存
由于commit limit是一个全局资源,它的消耗会导致低性能、应用程序失败甚至系统失败,自然就有人问了,诸多进程,对于commit charge有多大贡献呢?要准确的回答这个问题,我们需要理解应用程序可以分配的不同类型的虚存。
并不是所有由进程分配的虚存都要算到commit limit里,正如你看到的,预留的虚存就不能算。代表磁盘文件的虚存,或称为文件映像视图,同样也不算在内,除非应用程序要求copy-on-write(写时复制),因为Windows会丢弃物理内存中的数据,并从文件中读取。Testlimit地址空间中可执行代码或者系统DLL的镜像所占的虚存是不能算到commit limit里。只有两种类型的进程虚存要算到commit limit里:私有虚存和后端页文件(pagefile-backed)。
私有虚存,主要用于支持堆的垃圾回收、原生的堆和语言里的内存分配器,称其为私有是因为从定义角度来讲,这类虚存在进程间是不能共享的。基于这个原因,它也容易被定义到一个进程里,便于Windows通过私有的字节性能计数器来追踪它的使用情况。进程管理器通过Private Bytes列来显示有多少字节已经使用了,看看下边的示意图:
Pagefile-backed(后端支持页)虚存,相对难以界定,因为它是进程间共享的,事实上,也没有与进程相关的计数器来告诉你一个进程分配了或者引用了多少后端支持页。当你运行Testlimit -s时,它分配后端支持页虚存,直到达到commit limit,但是,即使耗光了所有的29GB,进程的虚存统计仍然不会指出它是出问题的那个进程:
由于这个原因,添加了-l参数来处理。一个进程必须要打开一个后端支持页虚存对象,称为节,来为它的地址空间创建一个后端支持页的虚存映射表。当Windows预留现有虚存(即使某应用关掉了该节的句柄时)时,大多数应用会保持该句柄为打开状态。-l开关会打印分配的节的大小。这时使用-s开关的Testlimit的部分输出结果:
你会看到,Testlimit正在分配以1MB为单位的块。
3. 页文件大小应该建为多大?
这是经常问的问题,似乎也没什么标准答案,甚至微软也会给出误导人的建议。几乎所有的建议都基于RAM大小的若干倍,通常1.2、1.5或者2。既然你知道了页文件在系统的commit limit中的作用,以及进程对commit charge的作用,你就会知道,这些公式基本没什么用。
因为commit limit设定了所有进程能够分配的私有虚存和后端支持页虚存的总和的上限,唯一确定合理页大小的方法是知道你同时运行的程序的总的commit charge的最大值,如果commit limit比那个值小,你的程序就可能不会正常工作。
如何知道commit charge呢?注意到windows会追踪一个值:Peak commit charge,要优化页大小,你应该同时启动所有应用程序,加载典型的数据集,注意监测commit charge值,把页大小的最小值设为该值减去系统的物理内存 (如果值为负,则挑一个最小值以允许crash dump)。如果你想多一些空间,可以把该值乘以2。
有些人可能会认为没有页文件会达到更好的性能,但是通常情况下,拥有页文件,意味着windows可以将脏数据回写到页文件,从而可以让那块内存提供给更多的进程或者缓存。通常情况下,拥有虚存页意味着可以分配更多的内存给系统使用。
页文件的配置是系统属性,运行命令”sysdm.cpl"即可进入配置框。
你会注意到,windows的默认配置是自动管理页文件大小。针对XP和2003系统,RAM不超过1G时,windows会创建1.5倍大小的页文件,超过1G时,会创建最小RAM大小的虚存页文件。在Vista或2008下,最小值是至少能容纳一个内核内存崩溃转储文件,RAM加上300M,或者是1G,取其中的最大值。最大值是RAM大小的3倍,或者4GB。这也解释了在8GB内存的64位系统里,peak commit值最大曾经达到32GB,页大小的24GB加上物理内存的8GB。
与虚存相关的一些限制,主要是虚存的最大值以及页文件的数目。32位的windows最大页文件大小为16TB(如果在非-PAE模式下,最大也只能是4GB),64位windows可以将页大小弄到16TB (x64), 在IA64架构下,可以搞到32TB。而Windows8的ARM架构下,页大小最大只能是4GB,以所有版本,Winodws支持最多16个页文件,并且每个页文件必须位于不同的卷(分区)。具体限制列表如下:
Version | Limit on x86 w/o PAE | Limit on x86 w/PAE | Limit on ARM | Limit on x64 | Limit on IA64 |
Windows 7 | 4 GB | 16 TB | 16 TB | ||
Windows 8 | 16 TB | 4 GB | 16 TB | ||
Windows Server 2008 R2 | 16 TB | 32 TB | |||
Windows Server 2012 | 16 TB |