2

我最近在我的程序中看到了一个奇怪的行为。创建大量对象(500MB RAM)然后释放它们后,程序的内存占用不会恢复到其原始大小。它仍然显示 160MB(私有工作集)的占用空间。

正常行为?

Borland 的内存管理器的行为不是这样,所以如果可能,请确认(或确认)这是 FastMM 的正常行为:如果您有一个方便的程序,在其中创建了一个相当复杂的 MDI 子项(包含多个控件/对象),可以您在内存中(同时)在循环中创建该 MDI 子项的 250 个实例,然后将它们全部释放并检查内存占用。请确保您使用这些 MDI 孩子至少消耗 200-300MB 或 RAM。

特别是那些仍在使用 Delphi 7 的人可以通过暂时禁用 FastMM 来看到差异。

谢谢


如果有人感兴趣,特别是如果你想证明这不是内存泄漏(我希望这不是我的代码中的内存泄漏——这也是这篇文章的重点之一:检查是否是我的错),以下是原始讨论:

我的程序永远不会释放内存。为什么?
如何说服内存管理器释放未使用的内存

4

5 回答 5

3

亲爱的祭坛,我对你的猜测如此离题以及你如何不听人们之前多次告诉你的话感到眼花缭乱。

让我们把一些事情弄清楚。内存管理 101. 请仔细阅读。

在 Delphi 中分配内存时,涉及到两个内存管理器。

系统内存管理器

第一个是系统内存管理器。这个内置在 Windows 中,它以 4kb 大小的页面提供内存。

但它并不总是为您提供 RAM(或物理内存)中的内存。您的数据可以保存在硬盘上,并在每次需要访问时回读。这非常慢。

换句话说,假设您有 512Mb 的物理内存。您运行两个程序,每个程序都需要 1Gb 的内存。操作系统是做什么的?

它同意这两个请求。两个应用程序各获得 1Gb 的内存。两人都认为所有的记忆都在“记忆中”。但实际上,RAM 中只能保存 512Mb。其余的存储在页面文件中,尽管您的应用程序不知道这一点。它只是工作缓慢。

工作集大小

现在,您正在测量的“工作集大小”是什么?

它是保存在 RAM 中的已分配内存的一部分。

如果您有一个分配 1Gb 内存的应用程序,而您只有 512 Mb 的 RAM,那么它的工作集大小将为 512Mb。虽然它“使用”了 1Gb 的内存!

当您运行另一个需要内存的应用程序时,操作系统将通过将很少使用的“内存”块移动到硬盘驱动器来自动释放一些 RAM。

您的虚拟内存分配将保持不变,但更多页面将在硬盘驱动器上,而在 RAM 中则更少。工作集大小将减小。

由此,您应该已经明白,尝试最小化工作集大小是没有意义的。你一事无成。从任何意义上说,您都没有释放内存。您只是将数据卸载到硬盘上。

但是系统会在需要时自动执行此操作。除非需要,否则在 RAM 中腾出空间是没有意义的。你只是在减慢你的应用程序,仅此而已。

TLDR:“工作集大小”不是“应用程序使用多少内存”。它是“现在准备了多少”。不要试图减少它,你只会让事情变得更糟。

德尔福内存管理器

操作系统为您提供 4Kb 页面的虚拟内存。但通常你需要小得多的块。例如,您的整数需要 4 个字节,或者某些结构需要 32 个字节。解决方案?

应用程序内存管理器,例如 FastMM 或 BorlandMM 或其他。

它的工作是从操作系统的页面中分配内存,然后在需要时为您提供这些页面的一小块。

换句话说,当您请求 14 字节的内存时,会发生以下情况:

  1. 你向 FastMM 索要 14 字节的内存。
  2. FastMM 向操作系统请求 1 页内存(4096 字节)。
  3. 操作系统授予一页内存,用 RAM 备份它(它存储在实际 RAM 中)。
  4. FastMM 保存该页面,削减 14 个字节并提供给您。

当您要求另外 14 个字节时,FastMM 只会从同一页中再剪切 14 个字节。

当你释放内存时会发生什么?反过来也是一样的:

  1. 您向 FastMM 释放 14 个字节。没发生什么事。
  2. 你又释放了 14 个字节。FastMM 看到它分配的 4096 字节页面现在完全未使用。
  3. 因此它释放页面,将其返回给系统。

值得注意的是,FastMM不能只向系统释放 14 个字节。它必须在页面中释放内存。在整个页面空闲之前,FastMM 什么都做不了。没有人可以。

那么,为什么我的工作集大小如此之大,即使我发布了所有内容?

首先,您的工作集大小不是您应该测量的。虚拟内存消耗是。但是如果你有很大的工作集大小,你的虚拟内存消耗也会很高。

有什么问题?到这一步你应该就能猜出来了。

假设您分配了 1kb,然后分配了 3kb 的内存。你分配了多少虚拟内存?4kb,1 页。

现在你释放 3Kb。你现在使用多少虚拟内存?1KB?不,它仍然是 1 页。您不能从系统中分配少于 1 页。您仍在使用 4096 字节的虚拟内存。

想象一下,如果你这样做 1000 次。1kb、3kb、1kb、3kb、1kb、3kb 等等。您像这样分配 1000 * 4kb = 4 mb,然后释放所有 3kb 部分。你现在使用多少虚拟内存?

仍然是 4 mb。因为你一开始分配了 1000 页。在每一页中,您占用了 1kb 和 3kb 块。即使你释放了 3kb 的块,1kb 的块将继续保留你在内存中分配的每一页。每页占用 4kb 的虚拟内存。

内存管理器不能神奇地将所有 1kb 块“移动”在一起。这是不可能的,因为它们的虚拟地址可以从代码中的某个地方引用。这不是 FastMM 的特征。

但是为什么使用 BorlandMM 一切都会更好呢?

巧合。也许 BorlandMM 以与 FastMM 稍有不同的方式为您提供记忆。接下来你知道,你在你的应用程序中改变了一些东西,BorlandMM 的行为就像 FastMM 一样。内存管理器不可能完全防止这种影响,称为内存碎片。

那我该怎么办?

简短的回答是,在这困扰您之前不多。

你看,使用现代操作系统,你并没有真正吃掉任何人的内存。如上所述,当其他应用程序需要 RAM 时,操作系统会自动将您的页面换出。这不应该是一个问题。

并且“过多”的记忆并没有丢失。尽管分配了页面,但每个页面的 3kb 都被标记为“空闲”。下次您的应用需要内存时,内存管理器将使用该空间。

但是如果你真的想帮助它,你应该重新组织你的分配,以便你计划保留的那些首先完成,你将很快释放的那些都在之后分配。

像这样:1kb、1kb、1kb、……、3kb、3kb、3kb……

如果您现在释放所有 3kb 块,您的虚拟内存消耗将显着下降。

这并不总是可能的。如果不可能,那就什么都不做。它或多或少像它一样。

还有PS

首先,您不应该分配 500 个表单。这显然不是一条路。解决这个问题,您甚至不需要考虑内存分配和释放。

我希望这可以解决问题,因为坦率地说,关于同一主题的四个帖子有点太多了。

于 2010-12-27T14:18:14.547 回答
2

IIRC,Delphi 内存管理器不会立即将释放的内存返回给操作系统。

内存以小、中、大大小的块的形式分配,称为块。这些块在其内容被处理后会保留一段时间,以便在之后请求另一个分配时随时可用。

这限制了连续分配多个对象所需的系统调用量,并有助于避免堆碎片。

于 2010-12-21T12:59:21.937 回答
2

确认:Delphi 2007,默认内存管理器(应该是 FastMM 变体)。对重物的几项测试:

  1. 初始内存 2Mb,峰值内存 30Mb,最终内存 4Mb。
  2. 初始内存 2Mb,峰值内存 1Gb,最终内存 5.5Mb。
于 2010-12-21T13:10:14.210 回答
1

关于仍然分配 160MB 的堆管理器统计信息 (GetHeapStatus) 是什么?

于 2010-12-21T15:35:31.880 回答
1

解决了

为了确认此行为是由 FastMM 生成的(正如 Barry Kelly 所建议的),我创建了第二个分配大量 RAM 的程序。一旦 Windows 用完 RAM,我的程序内存利用率就会恢复到原来的值。

问题解决了。特别感谢 Barry Kelly,唯一一个指出真正“问题”的人。

于 2010-12-27T16:41:50.300 回答