几个月前,一位同事向我提到,我们的一个内部 Delphi 应用程序似乎占用了 8 GB 的 RAM。我告诉他了:
That's not possible
32 位应用程序只有 32 位虚拟地址空间。即使存在内存泄漏,它最多可以消耗 2 GB 的内存。之后分配将失败(因为虚拟地址空间中没有空白空间)。在内存泄漏的情况下,虚拟页面将被交换到页面文件中,从而释放物理 RAM。
但他指出,Windows 资源监视器表明系统上可用的 RAM 不足 1 GB。虽然我们的应用程序只使用了 220 MB 的虚拟内存:关闭它可以释放 8 GB 的物理 RAM。
所以我测试了它
我让应用程序运行了几个星期,今天我终于决定测试它。
首先,我在关闭应用程序之前查看内存使用情况:
我使用资源监视器来检查应用程序使用的内存以及正在使用的总 RAM:
然后关闭应用程序后的内存使用情况:
我还使用 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?
它就在那里;消耗内存。
session 私有(private)
关于“ session 私有(private)”内存的唯一信息来自 a blog post announcing RAMMap :
Session Private: Memory that is private to a particular logged in session. This will be higher on RDS Session Host servers.
这是一款什么样的应用
这是一个 32 位 native Windows 应用程序(即不是 Java,不是 .NET)。因为它是 native 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();
}
}
最佳答案
很可能在您的应用程序中的某个地方,您正在分配系统资源而不是释放它们。任何创建对象并返回句柄的 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 的 session 内存。应用程序内存消耗仍然很低,因为对象不是在应用程序的堆上创建的。
但是,在不了解您的应用程序的更多信息的情况下,很难更具体。以上是展示您正在观察的行为的一种方式。除此之外,我认为您需要调试。
在这种情况下,分配了大量 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 对象,而是搜索句柄(属于内核对象的一般类别)。在这里我们可以看到进程使用了大量的句柄。同样,进程内存消耗很低,但使用的 session 内存却大幅增加。
类似地,对象可能是用户对象——它们是通过调用诸如
CreateWindow
之类的东西创建的。 , CreateCursor
,或通过设置 Hook SetWindowsHookEx
.有关创建对象并返回每种类型的句柄的 WinAPI 调用列表,请参阅:Handles and Objects : Object Categories -- MSDN
通过将问题缩小到可能导致问题的调用类型,这可以帮助您开始追踪问题。如果您正在使用任何第三方组件,它也可能位于有问题的第三方组件中。
像 AQTime 这样的工具可以分析 Windows 分配,但我不确定是否有支持 Delphi5 的版本。可能还有其他分配分析器可以帮助跟踪这一点。
关于windows - 我的 32 位应用程序可以做什么来消耗数 GB 的物理 RAM?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32357314/