本文主要讲述windows平台下应用程序性能测试的内存相关的知识,通过本文了解内存基本原理和分析内存占用问题。
1内存分为物理内存和虚拟内存
物理内存指通过物理内存条而获得的内存空间,虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间)。
2两者都有系统约定的最大值
进程占用的内存一般是指物理内存,其中操作系统为每个进程的工作集定义了一个最小和最大工作集。每个进程的 工作集有最小工作集(20-50M)最大是45-345M
虚拟内存:每个32位的进程操作系统为其分配最大4G的线性虚拟地址空间,地址是从0X00000000~0XFFFFFFFF 其中低的2G留给进程,高的2G留给系统。
3查看物理内存和虚拟内存
1电脑配置
例如我的电脑配置是 :如下,安装的内存是16G。查看资源监视器里显示,所以平时所说的内存占用是指物理内存。
2内存在资源管理器概念
例如这里进程: 物理内存(工作集)=可共享+专用
工作集:是私人工作集中的内存数量与进程正在使用且可以由其他进程共享的内存数量的总和。 专用工作集:- 是工作集的一个子集,专用工作集专门描述了某个进程正在使用的且无法与其他进程共享的内存数量。 提交大小:是为某进程使用而保留的虚拟内存的数量。
3虚拟内存查看
虚拟内存,用VMMAP工具查看。如下图
具体含义可以去网上搜索。其中虚拟内存有三种状态:
1)自由(free)是指内存还未使用。
2)当申请内存使用VirtualAlloc传入MEM_RESERVE执行预留(reserved)操作。
3)当真正访问内存的数据才执行提交(committed),传入MEM_COMMINT参数。
1系统使用PFN数据库
系统使用PFN(Page Frame Number)数据库来存储物理内存。分几类page list来存储,如下面的图
可缓存内存是指在smodified和standby上的,可用的内存是指在zero \free\standby lists,如果这个值小于800M的话。windows就会消减工作集,会导致整体性能变差。
2操作过程
1)windows启动时,所有的内存全部是在 free page list.当进程请求内存时,(我理解为发生一次错误,从zero page file)。进程退出,则会将内存还到 free page list。
2)操作系统有两个线程会执行一些操作,一个是zero page thread,当要将free page移动到zeroed page时 进行置0 free page
3)第二个是 modified page writer,将modified page list移动到 standby page list时,进行第一次写出任何数据
页错误
1什么是页错误
访问数据时,进行虚拟地址映射到物理地址过程中,硬件检查页表时,发现所访问的页面不在内存,就产生异常--缺页异常,这个缺页异常就叫做页错误。
操作系统会执行缺页异常处理程序:获得磁盘的地址、启动磁盘、将该页调入内存。
如下图,是<<微软核心编程>>里例子,当访问的数据不在内存中就会发生一次fault,其中当访问page不在modified和standy里,则会发生HardPageFault。HardPageFault需要去系统的pagefile.sys里查找,这个查找过程会产生大量的IO操作,影响性能。
2 页错误的类型
Transition:是指访问的page是指在 modify或者stadby page list上
DemandZero:进程请求内存是,调用是zero page list
HardPageFault:访问的page不在工作集里,需要去磁盘pagefile去查找
copy on write:写入copy-on-write page。例如你要hook一个kernel上的函数,就是操作kernel上的page,需要先拷贝一份,这样不会影响其它进程使用kernel上的函数,这个操作就会发生一次copy on write错误
内存的分配API
1)利用 HeapAlloc 方法或 C/C++ 运行时中的 malloc 或 new 来进行堆内存分配。
2)利用 VirtualAlloc 方法从系统中直接分配内存。VirtualAlloc是Windows提供的API,通常用来分配大块的内存
3)由内核通过 CreateFile, CreateEvent, or CreateThread 等 Kernel32 APIs ,来代表应用程序进行处理
4)利用 User32 和 Gdi32 APIs 来处理 GDI 和 USER 。(默认情况下,每一个线程都有 10,000 处理( 10,000 handles )配额)
1刷内存
刷内存SetProcessWorkingSetSize
1原理
函数用来设置应用程序最小和最大的运行空间,只会保留需要的内存,例如我们的部分exe里是有刷内存,这里设置的最小8M,最大14M
2缺点
刷内存只是将可能暂时不需要工作集swap出去,如果业务又再需要,需从虚拟内存的pagefile里调用过来,这个过程反而降低系统性能,所以不推荐使用
2减少页错误
这里推荐的操作是预处理,减少随机IO等。
3查找占用不合理和分配不合理的地方
1例子:某个dll 申请内存不合理
分析过程
1)抓取对应exe启动过程中VirtualAlloc。
2)查看启动过程中过程中VirtualAlloc分配的类型是AIFO【AIFO是指在此阶段分配但未释放,这种就是可疑的分配点】。发现其中有一段每次分配495K
3)查看对应的分配堆栈如下,发现是调用CreateToolhelp32Snapshot方法,引起这次分配。查看系统API。CreateToolhelp32Snapshot可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照。
4)查看源码,发现这里是每隔3S,去查询系统所有进程的一个快照,所以每次大小都有接近500K。而且场景的触发频率非常高,每隔1S检测一次,后面跟开发核实,这里的逻辑不应如此设计
5)通过这个问题,我们可以去系统检索一个分配内存大的函数,例如CreateToolhelp32Snapshot,是不是所有触发逻辑合理,轮询调用是非常不合理,应该要规避,而且这个代码是常驻的。这个留在后面形成相应的规则
2
例子:是内存泄露还是?
例子:某个版本的资源挂机突然VM增加,是内存泄露还是?
1)现象:如下图一个内部版本,在某个长时间挂机,突然出现在1个小时和4个小时后,内存增长10M的样子。时间跨度长,如何获取增长时的内存分配堆栈?
方法一、在内存增长时,trace。缺点:无法准确捕获这个时刻
方法二、看图,应该在1个半小时能出现,一直trace这个过程的VirtualAlloc和heapAlloc。因xperf开启heapalloc 消耗太大,只能针对指定进程进行trace
2)复现过程:
Xperf -on PROC_THREAD+LOADER+CSWITCH+DISPATCHER+VIRT_ALLOC -stackwalkCSwitch+ReadyThread
xperf -start HeapSession -heap -Pids 18908 -BufferSize 1024 -MinBuffers 128 -MaxBuffers 128 -stackwalkHeapAlloc+HeapRealloc
注:第二条是开启heapsession来trace 指定进程的heapalloc。整个过程etl太大,全部需要输出到文件模式
如下图,查看到这个过程有几个分配大的点。其中有一个1M到4M,增长3M的情况和对应的堆栈。
其中0xfd10000这个对象分配3.176M,这个堆栈全部都是系统的API
性能里调优内存涉及的点比较多,上面几个例子只是部分。建议平时先建设基础基准数据,有业务增加及时定位。
附我们内存优化的一些方向:
https://blogs.msdn.microsoft.com/tims/2010/10/29/pdc10-mysteries-of-windows-memory-management-revealed-part-two/