我的32位应用程序可以做些什么来消耗千兆字节的物理RAM?

时间:2015-09-02 15:32:22

标签: windows delphi winapi virtual-memory delphi-5

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

  

那是不可能的

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

但他指出,Windows资源监视器表明系统上可用的RAM不到1 GB。虽然我们的应用程序仅使用220 MB的虚拟内存:关闭它可以释放8 GB的物理RAM。

所以我测试了它

我让应用程序运行了几周,今天我终于决定测试它。

首先我在关闭应用程序之前查看内存使用情况:

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

enter image description here

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

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

enter image description here

关闭应用后的内存使用情况:

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

enter image description here

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

| 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 |

enter image description here

注意: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 | 

enter image description here

当然没有对EMS,XMS,AWE等做任何事情。

32位非管理员应用程序可能会发生什么,导致Windows分配额外的7 GB RAM?

  • 它不是换出项目的缓存
  • 它不是SuperFetch缓存

它只是那里;消耗RAM。

会话专用

关于" Session Private" 内存的唯一信息来自a blog post announcing RAMMap

  

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

这是什么类型的应用

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

应该注意的是,我并没有要求人们调试应用程序;我希望Windows开发人员知道为什么Windows可能会保留我从未分配过的内存。话虽如此,最近(最近2年或3年)唯一可能导致此类事情发生变化的功能是每5分钟截取一次屏幕截图并将其保存到用户的%LocalAppData%文件夹中。计时器每五分钟触发一次:

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();
    }
}

1 个答案:

答案 0 :(得分:24)

您应用程序中的某个位置很可能是分配系统资源而不是释放它们。任何创建对象并返回句柄的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对象,这不一定是个问题。)

enter image description here

相比之下,在@ 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对象,而是搜索句柄(属于内核对象的一般类别)。在这里我们可以看到该进程使用了​​大量句柄。同样,进程内存消耗很低,但会话内存使用量大幅增加。

enter image description here

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

Handles and Objects : Object Categories -- MSDN

这可以帮助您通过将问题范围缩小到可能导致问题的呼叫类型来开始追踪问题。它可能也在一个有缺陷的第三方组件中,如果您使用任何组件。

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