为什么我的 Delphi 程序的内存继续增长? [英] Why does my Delphi program's memory continue to grow?

查看:27
本文介绍了为什么我的 Delphi 程序的内存继续增长?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是内置 FastMM4 内存管理器的 Delphi 2009.

我的程序读入并处理一个大型数据集.每当我清除数据集或退出程序时,所有内存都会被正确释放.它根本没有内存泄漏.

使用在 spenwarr 的回答中给出的 CurrentMemoryUsage 例程:如何获取Delphi程序使用的内存,我已经显示了FastMM4在处理过程中使用的内存.

似乎正在发生的是,在每个进程和发布周期之后,内存使用量都在增长.例如:

在没有数据集的情况下启动我的程序后使用了 1,456 KB.

加载大型数据集后使用了 218,455 KB.

完全清除数据集后的 71,994 KB.如果我此时退出(或我示例中的任何一点),则不会报告内存泄漏.

再次加载相同数据集后使用了 271,905 KB.

完全清除数据集后的 125,443 KB.

再次加载相同数据集后使用了 325,519 KB.

完全清除数据集后的 179,059 KB.

再次加载相同数据集后使用了 378,752 KB.

在每个加载/清除周期中,我的程序的内存使用量似乎增加了大约 53,400 KB.任务管理器确认这确实发生了.

我听说 FastMM4 在对象被释放时并不总是将程序的所有内存释放回操作系统,以便在需要更多内存时保留一些内存.但是这种持续的增长让我感到困扰.由于没有报告内存泄漏,我无法确定问题.

有谁知道为什么会发生这种情况,如果它很糟糕,以及我是否可以或应该对此做些什么?

<小时>

感谢 dthorpe 和 Mason 的回答.你让我思考和尝试让我意识到我错过了什么的事情.所以需要详细的调试.

事实证明,我的所有结构在退出时都被正确释放.但是在运行期间每个循环后的内存释放不是.它正在积累通常会导致泄漏的内存块,如果我的退出清理不正确,则在退出时可以检测到该泄漏 - 但确实如此.

在循环之间我需要清除一些 StringList 和其他结构.我仍然不确定我的程序如何与早期周期中仍然存在的额外数据一起正常工作,但确实如此.我可能会进一步研究.

这个问题已经回答了.感谢您的帮助.

解决方案

您链接到的 CurrentMemoryUsage 实用程序报告您的应用程序的工作集大小.工作集是映射到物理内存地址的虚拟内存地址空间的总页数.但是,这些页面中的一些或许多页面中存储的实际数据可能很少.因此,工作集是您的进程使用多少内存的上限".它表示保留了多少地址空间以供使用,但并不表示实际提交的地址空间(实际驻留在物理内存中)或提交的页面中有多少被您的应用程序实际使用.

试试这个:当你看到你的工作集大小在几次测试运行后逐渐增加后,最小化你的应用程序的主窗口.您很可能会看到工作集大小显着下降.为什么?因为当您最小化应用程序时,Windows 会执行 SetProcessWorkingSetSize(-1) 调用,该应用程序会丢弃未使用的页面并将工作集缩小到最小.当应用程序窗口大小正常时,操作系统不会这样做,因为过于频繁地减小工作集大小会强制从交换文件重新加载数据,从而降低性能.

更详细地了解它:您的 Delphi 应用程序以相当小的块分配内存——这里是一个字符串,那里是一个类.程序的平均内存分配通常少于几百字节.很难在系统范围内有效地管理这样的小分配,因此操作系统不会.它有效地管理大内存块,特别是在 4k 虚拟内存页面大小和 64k 虚拟内存地址范围最小大小时.

这给应用程序带来了一个问题:应用程序通常分配小块,但操作系统以相当大的块分配内存.该怎么办?答案:子分配.

Delphi 运行时库的内存管理器和 FastMM 替代内存管理器(以及地球上几乎所有其他语言或工具集的运行时库)都为了做一件事:将大内存块从操作系统分割成更小的块应用程序使用.跟踪所有小块的位置、它们有多大以及它们是否被泄漏"也需要一些内存 - 称为开销.

在大量内存分配/释放的情况下,可能会出现您释放 99% 已分配内容的情况,但进程的工作集大小仅缩小了 50%.为什么?大多数情况下,这是由堆碎片引起的:一小块内存仍在使用 Delphi 内存管理器从操作系统获取并在内部分配的大块之一.使用的内存的内部计数很小(比如说 300 字节),但是由于它阻止堆管理器将它所在的大块释放回操作系统,所以这个 300 字节的小块的工作集贡献更像是 4k(或64k 取决于它是虚拟页面还是虚拟地址空间 - 我不记得了).

在涉及兆字节小内存分配的大量内存密集型操作中,堆碎片非常常见 - 特别是如果与内存密集型操作无关的事情的内存分配与大作业同时进行.例如,如果在处理 80MB 数据库操作时也将状态输出到列表框,则用于报告状态的字符串将分散在数据库内存块的堆中.当您释放数据库计算使用的所有内存块时,列表框字符串仍然在那里(在使用中,没有丢失)但它们分散在各处,每个小字符串可能会占用整个操作系统大块.

尝试最小化窗口技巧,看看是否会减少您的工作集.如果是这样,您可以忽略工作集计数器返回的数字的明显严重性".您还可以在大型计算操作后添加对 SetProcessWorkingSetSize 的调用,以清除不再使用的页面.

I am using Delphi 2009 which has the FastMM4 memory manager built into it.

My program reads in and processes a large dataset. All memory is freed correctly whenever I clear the dataset or exit the program. It has no memory leaks at all.

Using the CurrentMemoryUsage routine given in spenwarr's answer to: How to get the Memory Used by a Delphi Program, I have displayed the memory used by FastMM4 during processing.

What seems to be happening is that memory is use is growing after every process and release cycle. e.g.:

1,456 KB used after starting my program with no dataset.

218,455 KB used after loading a large dataset.

71,994 KB after clearing the dataset completely. If I exit at this point (or any point in my example), no memory leaks are reported.

271,905 KB used after loading the same dataset again.

125,443 KB after clearing the dataset completely.

325,519 KB used after loading the same dataset again.

179,059 KB after clearing the dataset completely.

378,752 KB used after loading the same dataset again.

It seems that my program's memory use is growing by about 53,400 KB upon each load/clear cycle. Task Manager confirms that this is actually happening.

I have heard that FastMM4 does not always release all of the program's memory back to the Operating system when objects are freed so that it can keep some memory around when it needs more. But this continual growing bothers me. Since no memory leaks are reported, I can't identify a problem.

Does anyone know why this is happening, if it is bad, and if there is anything I can or should do about it?


Thank you dthorpe and Mason for your answers. You got me thinking and trying things that made me realize I was missing something. So detailed debugging was required.

As it turns out, all my structures were being properly freed upon exit. But the memory release after each cycle during the run was not. It was accumulating memory blocks that would normally have caused a leak that would have been detectable on exit if my exit cleanup was not correct - but it was.

There were some StringLists and other structures I needed to clear between the cycles. I'm still not sure how my program worked correctly with the extra data still there from the earlier cycles but it did. I'll probably research that further.

This question has been answered. Thanks for your help.

解决方案

The CurrentMemoryUsage utility you linked to reports your application's working set size. Working set is the total number of pages of virtual memory address space that are mapped to physical memory addresses. However, some or many of those pages may have very little actual data stored in them. The working set is thus the "upper bound" of how much memory your process is using. It indicates how much address space is reserved for use, but it does not indicate how much is actually committed (actually residing in physical memory) or how much of the pages that are committed are actually in use by your application.

Try this: after you see your working set size creep up after several test runs, minimize your application's main window. You will most likely see the working set size drop significantly. Why? Because Windows performs a SetProcessWorkingSetSize(-1) call when you minimize an application which discards unused pages and shrinks the working set to the minimum. The OS doesn't do this while the app window is normal sized because reducing the working set size too often can make performance worse by forcing data to be reloaded from the swap file.

To get into it in more detail: Your Delphi application allocates memory in fairly small chunks - a string here, a class there. The average memory allocation for a program is typically less than a few hundred bytes. It's difficult to manage small allocations like this efficiently on a system-wide scale, so the operating system doesn't. It manages large memory blocks efficiently, particularly at the 4k virtual memory page size and 64k virtual memory address range minimum sizes.

This presents a problem for applications: applications typically allocate small chunks, but the OS doles out memory in rather large chunks. What to do? Answer: suballocate.

The Delphi runtime library's memory manager and the FastMM replacement memory manager (and the runtime libraries of just about every other language or toolset on the planet) both exist to do one thing: carve up big memory blocks from the OS into smaller blocks used by the application. Keeping track of where all the little blocks are, how big they are, and whether they've been "leaked" requires some memory as well - called overhead.

In situations of heavy memory allocation/deallocation, there can be situations in which you deallocate 99% of what you allocated, but the process's working set size only shrinks by, say, 50%. Why? Most often, this is caused by heap fragmentation: one small block of memory is still in use in one of the large blocks that the Delphi memory manager obtained from the OS and divvied up internally. The internal count of memory used is small (300 bytes, say) but since it's preventing the heap manager from releasing the big block that it's in back to the OS, the working set contribution of that little 300 byte chunk is more like 4k (or 64k depending on whether it's virtual pages or virtual address space - I can't recall).

In a heavy memory intensive operation involving megabytes of small memory allocations, heap fragmentation is very common - particularly if memory allocations for things not related to the memory intensive operation are going on at the same time as the big job. For example, if crunching through your 80MB database operation also outputs status to a listbox as it progresses, the strings used to report status will be scattered in the heap amongst the database memory blocks. When you release all the memory blocks used by the database computation, the listbox strings are still out there (in use, not lost) but they are scattered all over the place, potentially occupying an entire OS big block for each little string.

Try the minimize window trick to see if that reduces your working set. If it does, you can discount the apparent "severity" of the numbers returned by the working set counter. You could also add a call to SetProcessWorkingSetSize after your big compute operation to purge the pages that are no longer in use.

这篇关于为什么我的 Delphi 程序的内存继续增长?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆