我的32位应用程序能做什么,消耗千兆字节的物理内存?

几个月前,一位同事向我提到,我们的一个Delphi应用程序似乎占用了8GB的内存。 我告诉他了:

这是不可能的

一个32位应用程序只有一个32位的虚拟地址空间。 即使有内存泄漏,它可以消耗的最多的内存也是2GB。 之后,分配将失败(因为在虚拟地址空间中不会有空的空间)。 而在内存泄漏的情况下,虚拟页面将被换出到页面文件,释放物理RAM。

但他指出,Windows资源监视器指出,系统上可用的RAMless于1 GB。 而我们的应用程序只使用了220 MB的虚拟内存:closures它释放了8 GB的物理RAM。

所以我testing了它

我让应用程序运行了几个星期,今天我终于决定testing它。

首先我看看closures应用程序之前的内存使用情况:

  • 工作集(RAM)是241 MB
  • 使用的总虚拟内存: 409 MB

在这里输入图像说明

我使用资源监视器检查应用程序使用的内存,以及使用的总RAM:

  • 由应用程序分配的虚拟内存: 252 MB
  • 正在使用的物理内存: 14 GB

在这里输入图像说明

然后closures应用程序后的内存使用情况:

  • 正在使用的物理内存: 6.6 GB (低7.4 GB)

在这里输入图像说明

我还使用Process Explorer来查看物理RAM使用前后的细分情况。 唯一的区别是,8 GB的内存真的没有提交,现在免费:

| Item | Before | After | |-------------------------------|------------|-----------| | Commit Charge (K) | 15,516,388 | 7,264,420 | | Physical Memory Available (K) | 1,959,480 | 9,990,012 | | Zeroed Paging List (K) | 539,212 | 8,556,340 | 

在这里输入图像说明

注意:有些有趣的是,Windows会浪费时间立即清零所有的内存,而不是简单地把它放在备用列表中,并根据需要将其清零(因为需要满足内存请求)。

这些事情都不能解释RAM在做什么 (你坐在那里干什么!你包含什么!?)

这是什么内存!

RAM必须包含一些有用的东西 ; 它必须有一些目的。 为此我转向了SysInternals的RAMMap 。 它可以分解内存分配。

RAMMap提供的唯一线索是8 GB的物理内存与Session Private相关联。 这些会话私人分配不与任何过程(即不是我的过程)相关联:

 | Item | Before | After | |------------------------|----------|----------| | Session Private | 8,031 MB | 276 MB | | Unused | 1,111 MB | 8,342 MB | 

在这里输入图像说明

当然不会对EMS,XMS,AWE等做任何事情

在32位非pipe理员应用程序中,可能会导致Windows分配额外的7 GB RAM?

  • 这不是换出项目的caching
  • 这不是一个超级caching

在那里 ; 耗费内存。

会议私人

关于“会话专用”内存的唯一信息来自宣布RAMMap的博客post :

会话专用:专用于特定login会话的内存。 在RDS会话主机服务器上这将更高。

这是什么types的应用程序

这是一个32位本地Windows应用程序(即不是Java,不是.NET)。 因为它是一个本地Windows应用程序,所以它当然会大量使用Windows API。

应该指出的是,我并没有要求人们debugging应用程序, 我希望Windows开发人员知道为什么Windows可能拥有我从未分配过的内存。 话虽如此,最近(过去2年或3年)发生的唯一可能导致这种事情的变化是每隔5分钟截取屏幕截图并将其保存到用户的%LocalAppData%文件夹中的function。 计时器每五分钟启动一次:

 QueueUserWorkItem(TakeScreenshotThreadProc); 

线程方法的伪代码:

 void TakeScreenshotThreadProc(Pointer data) { String szFolder = GetFolderPath(CSIDL_LOCAL_APPDTA); ForceDirectoryExists(szFolder); String szFile = szFolder + "\" + FormatDateTime('yyyyMMdd"_"hhnnss', Now()) + ".jpg"; Image destImage = new Image(); try { CaptureDesktop(destImage); JPEGImage jpg = new JPEGImage(); jpg.CopyFrom(destImage); jpg.CompressionQuality = 13; jpg.Compress(); HANDLE hFile = CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, null, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_ENCRYPTED, 0); //error checking elucidated try { Stream stm = new HandleStream(hFile); try { jpg.SaveToStream(stm); } finally { stm.Free(); } } finally { CloseHandle(hFile); } } finally { destImage.Free(); } } 

最有可能在你的应用程序的某个地方,你正在分配系统资源,而不是释放它们。 任何创建对象并返回句柄的WinApi调用都可能是一个嫌疑犯。 例如(小心在有限内存的系统上运行这个 – 如果你没有6GB的空闲空间,它将会严重的页面):

 Program Project1; {$APPTYPE CONSOLE} uses Windows; var b : Array[0..3000000] of byte; i : integer; begin for i := 1 to 2000 do CreateBitmap(1000, 1000, 3, 8, @b); ReadLn; end. 

这会消耗6GB的会话内存,因为未分配位图对象。 应用程序的内存消耗保持在低水平,因为对象不是在应用程序的堆上创建的。

但是,如果不了解更多关于应用程序的信息,则要更具体一些是非常困难的。 以上是展示你正在观察的行为的一种方法。 除此之外,我认为你需要调试。

在这种情况下,分配了大量的GDI对象 – 但这不一定是指示性的,因为在应用程序中通常会分配大量的小GDI对象,而不是大量的大对象(Delphi IDE ,例如,将会例行地创建> 3000个GDI对象,这不一定是个问题)。

在这里输入图像说明

在@ Abelisto的例子(在评论中),相比之下:

 Program Project1; {$APPTYPE CONSOLE} uses SysUtils; var i : integer; sr : TSearchRec; begin for i := 1 to 1000000 do FindFirst('c:\*', faAnyFile, sr); ReadLn; end. 

这里返回的句柄不是GDI对象,而是搜索句柄(属于内核对象的一般类别)。 在这里我们可以看到进程使用了​​大量的句柄。 同样,进程内存消耗也很低,但会话内存使用率有很大的提高。

在这里输入图像说明

同样,这些对象可能是用户对象 – 这些对象是通过调用CreateWindowCreateCursor或通过使用SetWindowsHookEx设置钩子来创建的。 有关创建对象并返回每种类型的句柄的WinAPI调用列表,请参阅:

句柄和对象:对象类别 – MSDN

这可以帮助您开始追踪问题,将其缩小到可能导致问题的呼叫类型。 如果您使用的是第三方组件,它也可能位于第三方组件中。

像AQTime这样的工具可以分析Windows分配,但我不确定是否有支持Delphi5的版本。 可能有其他的分配器分析器可以帮助追踪这个问题。