【发布时间】:2015-11-28 04:58:29
【问题描述】:
几个月前,一位同事向我提到,我们的一个内部 Delphi 应用程序似乎占用了 8 GB 的 RAM。我告诉他:
这是不可能的
32 位应用程序只有一个 32 位虚拟地址空间。即使发生内存泄漏,它最多可以消耗 2 GB 的内存。之后分配将失败(因为虚拟地址空间中没有空白空间)。并且在内存泄漏的情况下,虚拟页面将被换出到页面文件,从而释放物理 RAM。
但他指出,Windows 资源监视器表明系统上的可用 RAM 不足 1 GB。虽然我们的应用只使用了 220 MB 的虚拟内存:关闭它会释放 8 GB 的物理 RAM。
所以我测试了它
我让应用程序运行了几个星期,今天我终于决定测试它。
首先我在关闭应用程序之前查看内存使用情况:
- 工作集 (RAM) 为 241 MB
- 使用的总虚拟内存:409 MB
我使用资源监视器来检查应用程序使用的内存,以及正在使用的总 RAM:
- 应用程序分配的虚拟内存:252 MB
- 正在使用的物理内存:14 GB
然后是关闭应用后的内存使用情况:
- 正在使用的物理内存:6.6 GB (7.4 GB 以下)
我还使用 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 |
注意:有点有趣的是,Windows 会浪费时间立即将所有内存清零,而不是简单地将其放在备用列表中,然后根据需要将其清零(因为需要满足内存请求)。
这些都不能解释 RAM 在做什么(你在做什么就坐在那里!你包含什么!?)
那段记忆里有什么!
该 RAM 必须包含一些有用的东西;它必须有一些目的。为此,我求助于 SysInternals 的 RAMMap。它可以分解内存分配。
RAMMap 提供的唯一线索是 8 GB 物理内存与称为 Session Private 的东西相关联。这些 Session Private 分配不与任何进程(即不是我的进程)相关联:
| Item | Before | After |
|------------------------|----------|----------|
| Session Private | 8,031 MB | 276 MB |
| Unused | 1,111 MB | 8,342 MB |
我当然不会对 EMS、XMS、AWE 等做任何事情。
在 32 位非管理员应用程序中可能会发生什么导致 Windows 分配额外的 7 GB RAM?
- 这不是换出项目的缓存
- 它不是 SuperFetch 缓存
就在那里;消耗内存。
私人会话
关于“Session Private”内存的唯一信息来自a blog post announcing RAMMap:
Session Private:专用于特定登录会话的内存。这在 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();
}
}
【问题讨论】:
-
顺便说一句,人们通常应该很高兴发现您的 RAM 被使用了。把所有的 RAM 塞进去让它闲置有什么意义?
-
来自 Technet:每个用户都有自己的会话空间映射到虚拟内存中。会话空间分为四个不同的区域: 会话结构——内存管理控制结构,包括会话工作集列表。会话映像空间——保存 Win32k.sys 修改数据的私有副本、Win32k.sys 代码和未修改数据的单个副本以及各种会话驱动程序。会话视图空间——会话映射视图,包括桌面堆会话分页池——用于此会话的分页池内存
-
这可能是由于大量创建和未释放的windows资源(句柄)造成的。我们在一个项目中遇到了这样的问题。
-
非常简短的示例:在控制台应用程序中:
for i := 1 to 1000000 do FindFirst('c:\*', faAnyFile, sr);在此循环之后:应用程序 133M,总计 3GB,关闭应用程序:总计 1.5 GB -
虽然我从不相信伪代码是真实事物的准确表示,但在调试时,你似乎忘记释放你的
JPEGImage...
标签: windows delphi winapi virtual-memory delphi-5