为什么我释放内存后程序的内存使用量没有恢复正常?

7
考虑下面的样例应用程序。
program TestMemory;


{$APPTYPE CONSOLE}

uses
  PsAPI,
  Windows,
  SysUtils;

function GetUsedMemoryFastMem: cardinal;
var
    st: TMemoryManagerState;
    sb: TSmallBlockTypeState;
begin
    GetMemoryManagerState(st);
    result := st.TotalAllocatedMediumBlockSize + st.TotalAllocatedLargeBlockSize;
    for sb in st.SmallBlockTypeStates do
    begin
        result := result + sb.UseableBlockSize * sb.AllocatedBlockCount;
    end;
end;

function GetUsedMemoryWindows: longint;
var
  ProcessMemoryCounters: TProcessMemoryCounters;
begin
  Result:=0;
  ProcessMemoryCounters.cb := SizeOf(TProcessMemoryCounters);
  if GetProcessMemoryInfo(GetCurrentProcess(), @ProcessMemoryCounters, ProcessMemoryCounters.cb) then
   Result:= ProcessMemoryCounters.WorkingSetSize
  else
   RaiseLastOSError;
end;

procedure Test;
const
  Size = 1024*1024;
var
  P : Pointer;
begin
  GetMem(P,Size);

      Writeln('Inside');
      Writeln('FastMem '+FormatFloat('#,', GetUsedMemoryFastMem));
      Writeln('Windows '+FormatFloat('#,', GetUsedMemoryWindows));
      Writeln('');

  FreeMem(P);
end;

begin
      Writeln('Before');
      Writeln('FastMem '+FormatFloat('#,', GetUsedMemoryFastMem));
      Writeln('Windows '+FormatFloat('#,', GetUsedMemoryWindows));
      Writeln('');

      Test;

      Writeln('After');
      Writeln('FastMem '+FormatFloat('#,', GetUsedMemoryFastMem));
      Writeln('Windows '+FormatFloat('#,', GetUsedMemoryWindows));
      Writeln('');
      Readln;
end.

该应用程序返回的结果是

Before
FastMem 1.844
Windows 3.633.152

Inside
FastMem 1.050.612
Windows 3.637.248

After
FastMem 2.036
Windows 3.633.152

我想知道为什么在BeforeAfter中内存使用结果不同:


@Optimal Cynic - 这真的很聪明吗?我的应用程序似乎没有释放大约160MB的内存。在只有1GB或更少RAM的计算机上,这不是浪费RAM吗? 详情请见:https://dev59.com/o1LTa4cB1Zd3GeqPc7ib - Gabriel
3个回答

12
任何内存管理器(包括FastMM)都会带来一些开销,否则Delphi就可以直接使用Windows内存管理了。
你观察到的差异是开销:
  • FastMM用于跟踪内存使用情况的结构
  • FastMM尚未返回给Windows内存管理的内存块,以便将来优化类似的内存分配。

请问您能否评论一下“Inside - Windows”这个值? - Branko
@Branko,请解释一下你的评论,因为我不明白你对我有什么期望。 - Jeroen Wiert Pluimers
@JeroenWiertPluimers 可能(猜测)他正在谈论 OP 的 TestMemory 应用程序的返回值。内部...Windows 3.637.248 - EMBarbosa
@JeroenWiertPluimers - 在FastMem中,内部值和前面的值之间的差异为1,048,768,但在Windows中只有4,096? - Branko
@Branko 看起来你的程序大部分使用的是 Delphi 堆(由 FastMM 提供),几乎没有使用 Windows 堆。这很好,因为这是你通常从 Delphi 代码中期望的。 - Jeroen Wiert Pluimers

2

因为内存管理器在后台进行智能处理以提高性能。


例如什么?如果进程从内存中释放了大量的工作数据,那么就没有必要保留引用或其他内容,因此我认为,理想情况下,内存大小应该与执行前相同。 - user532231
1
理想情况下,取决于您更注重速度还是尺寸。 - Lars Truijens
而聪明也取决于上下文。一个极其聪明的内存管理器可能会加速和减少大型、复杂的多线程应用程序的占用空间 - 但代价是在“分配,然后立即释放”的情况下浪费内存。我不了解Delphi的内存管理器具体做了什么,但我知道你不能从问题中的示例推断出任何合理大小的应用程序。 - Optimal Cynic
1
顺便提一下,在正确(错误?)的情境下,聪明有时可以成为愚蠢的同义词。 - Optimal Cynic

0

getmem/malloc/free如何工作?

堆分配器 - 由malloc使用...

1)内部分配大块(通常为64K到1MB)的内存,然后将这些块细分以向程序中提供100字节和200字节的对象和字符串。当您释放内存时,发生的只是在内部缓冲区或块中分配它的位置标记为空闲状态。实际上什么也没有发生!

2)因此,您可以将堆视为一系列大块内存列表,您程序中的所有对象都是这些块的小部分。

3)当其中所有对象都被释放时,大块内部的内存才会被释放,因此通常情况下,当您释放某个对象时,除了一些位被标记为空闲之外,实际上什么都不会发生。

这是堆系统的相当简单的描述,但大多数堆的工作方式类似,但做了比那更多的优化。但您的问题是为什么内存不会减少,答案是因为实际上什么也没有被释放。内存的内部页面保留用于下一次调用“new”或“malloc”等函数。

形象化描述

堆内部有一个100Kb的巨块

You call "malloc(1000)" or "getmem(1000)" to get a 1K block of memory.

然后发生的一切就是从100kb内存块中取出1K内存块,使该块内存中还剩下99K可用内存。如果您继续调用malloc或getmem,则它将继续将较大的块分割,直到需要另一个更大的块。

使用malloc或getmem调用分配的每个小内存块实际上会获得额外的16或24个字节(取决于分配器)的额外内存。该内存是分配器用于知道已分配什么以及已分配在哪里的位。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接