简体   繁体   English

为什么我的 Delphi 程序的内存继续增长?

[英]Why does my Delphi program's memory continue to grow?

I am using Delphi 2009 which has the FastMM4 memory manager built into it.我正在使用 Delphi 2009,它内置了 FastMM4 内存管理器。

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.使用在 spenwarr 的回答中给出的 CurrentMemoryUsage 例程: How to get the Memory Used by a Delphi Program ,我已经显示了 FastMM4 在处理过程中使用的内存。

What seems to be happening is that memory is use is growing after every process and release cycle.似乎正在发生的是,在每个进程和发布周期之后,内存使用量都在增长。 eg:例如:

1,456 KB used after starting my program with no dataset.在没有数据集的情况下启动我的程序后使用了 1,456 KB。

218,455 KB used after loading a large dataset.加载大型数据集后使用了 218,455 KB。

71,994 KB after clearing the dataset completely.完全清除数据集后的 71,994 KB。 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.再次加载相同的数据集后使用了 271,905 KB。

125,443 KB after clearing the dataset completely.完全清除数据集后的 125,443 KB。

325,519 KB used after loading the same dataset again.再次加载相同的数据集后使用了 325,519 KB。

179,059 KB after clearing the dataset completely.完全清除数据集后的 179,059 KB。

378,752 KB used after loading the same dataset again.再次加载相同的数据集后使用了 378,752 KB。

It seems that my program's memory use is growing by about 53,400 KB upon each load/clear cycle.在每个加载/清除周期中,我的程序的内存使用量似乎增加了大约 53,400 KB。 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.我听说 FastMM4 在释放对象时并不总是将程序的所有内存释放回操作系统,以便在需要更多内存时保留一些内存。 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.感谢 dthorpe 和 Mason 的回答。 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.我需要在循环之间清除一些 StringLists 和其他结构。 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.您链接到的 CurrentMemoryUsage 实用程序报告您的应用程序的工作集大小。 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.因为当您最小化应用程序时,Windows 会执行 SetProcessWorkingSetSize(-1) 调用,该应用程序会丢弃未使用的页面并将工作集缩小到最小。 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.更详细地了解它:您的 Delphi 应用程序以相当小的块分配内存——这里是一个字符串,那里是一个类。 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.它有效地管理大内存块,特别是在 4k 虚拟内存页面大小和 64k 虚拟内存地址范围最小大小时。

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. Delphi 运行时库的内存管理器和 FastMM 替代内存管理器(以及地球上几乎所有其他语言或工具集的运行时库)都存在做一件事:将操作系统中的大内存块分割成由操作系统使用的较小块。应用。 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%.在大量内存分配/解除分配的情况下,可能会出现您解除分配 99% 的分配的情况,但进程的工作集大小仅缩小了 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.大多数情况下,这是由堆碎片引起的:一小块内存仍在使用 Delphi 内存管理器从操作系统获取并在内部分配的大块之一。 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).使用的内存的内部计数很小(比如说 300 字节),但是由于它阻止堆管理器将它所在的大块释放回操作系统,所以这个 300 字节的小块的工作集贡献更像是 4k(或64k 取决于它是虚拟页面还是虚拟地址空间 - 我不记得了)。

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.例如,如果在处理 80MB 数据库操作时也将状态输出到列表框,则用于报告状态的字符串将分散在数据库内存块的堆中。 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.您还可以在大型计算操作后添加对 SetProcessWorkingSetSize 的调用,以清除不再使用的页面。

What sort of dataset are you using?你使用什么样的数据集? If it's implemented completely in Delphi, (not calling out to other code with another memory manager, like Midas,) you could try deliberately leaking the dataset.如果它完全在 Delphi 中实现(而不是使用另一个内存管理器调用其他代码,例如 Midas),您可以尝试故意泄漏数据集。

I assume that your dataset is on a form, and it's being freed automatically when the form clears its components.我假设您的数据集位于表单上,并且在表单清除其组件时会自动释放它。 Try putting MyDataset := nil;尝试把MyDataset := nil; in your form's OnDestroy.在您的表单的 OnDestroy 中。 This will make sure that the dataset leaks, and also everything that the dataset owns.这将确保数据集泄漏,以及数据集拥有的所有内容。 Try that after loading once and again after loading twice and compare the leak reports, and see if that gives you anything useful.在加载两次后一次又一次地加载后尝试并比较泄漏报告,看看这是否对您有用。

You are half-leaking memory;你正在泄漏一半的记忆; obviously.明显地。 You are leaking memory while the program is running, but when you close the program, your dataset is properly freed so FastMM (rightfully) does not report it.您在程序运行时正在泄漏内存,但是当您关闭程序时,您的数据集已正确释放,因此 FastMM(理所当然)不会报告它。

See this for details: My program never releases the memory back.有关详细信息,请参阅: 我的程序从不释放内存。 Why? 为什么?

You could use VMMap to trace the most allocated bytes.您可以使用 VMMap 来跟踪分配最多的字节。 It helped me for an similar scenario.它帮助我解决了类似的情况。

  • Download VMMap下载VMMap
  • Compile your application with map file detailed使用详细的地图文件编译您的应用程序
  • Convert the map file to dbg, so VMMap can understand it.把map文件转成dbg,这样VMMap就可以理解了。 Use the map2dbg tool使用map2dbg工具
  • Configure symbol (dbg) path on VMMap: Options -> Configure Symbols -> Symbol paths在 VMMap 上配置符号 (dbg) 路径:选项 -> 配置符号 -> 符号路径
  • Configure source paths on VMMap -> Options -> Configure Symbols -> Source code paths.在 VMMap 上配置源路径 -> 选项 -> 配置符号 -> 源代码路径。 Hint: use the "*" to include subfolders提示:使用“*”包含子文件夹
  • In VMMap, go to File -> Select Process -> Launch and trace a new process.在 VMMap 中,转到 File -> Select Process -> Launch 并跟踪新进程。 Configure application and any parameter that it needs.配置应用程序和它需要的任何参数。 Then Ok.那么好的。

When the app opens, VMMap will trace all allocated and freed memory using detours in allocate/free methods.当应用程序打开时,VMMap 将使用分配/释放方法中的绕道来跟踪所有已分配和已释放的内存。 You can see in Timeline button (on the botton of VMMap) the timeline of memory (obviously).您可以在 Timeline 按钮(在 VMMap 的底部)中看到内存的时间线(显然)。

Click in Trace button.单击跟踪按钮。 It will show all the allocations/dealocattions operations in the traced time.它将显示跟踪时间内的所有分配/解除分配操作。 Order the column Bytes to show the most bytes first, and double click it.排列 Bytes 列首先显示最多的字节,然后双击它。 It will show the callstack of the allocation.它将显示分配的调用堆栈。 In my case, the first item showed my problem.就我而言,第一项显示了我的问题。

Sample app:示例应用程序:

private
  FList: TObjectList<TStringList>;
  ...
procedure TForm1.Button1Click(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to 1000000 do
    FList.Add(TStringList.Create);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  a: TStringList;
begin
  FList := TObjectList<TStringList>.Create; //not leak
  a := TStringList.Create; //leak
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FList.Free;
end;

When clicking in button one time, and see the Trace in VMMap, shows:当单击按钮一次,并在 VMMap 中看到 Trace 时,显示:

跟踪表

And the callstack:和调用堆栈:

调用栈

In this case did not show exactly the code, but the Vcl.Controls.TControl.Click give an idea.在这种情况下没有准确显示代码,但 Vcl.Controls.TControl.Click 给出了一个想法。 In my real scenario, helped more.在我的真实场景中,帮助更多。

There is a lot of others functionalities in VMMap that helps analising memory problems. VMMap 中有很多其他功能可以帮助分析内存问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么Delphi 5中的Delphi 6的MSXML库会泄漏内存? - Why does Delphi 6's MSXML library leak memory in Delphi 5? 为什么我的Delphi的Android模拟器不能运行应用程序? - Why my Delphi's Android Emulator does not run the applications? 为什么Delphi的内存管理器会在关机时报告错误的内存泄漏? - Why does Delphi's memory manager report false memory leaks at shutdown? Delphi:为什么这会产生 memory 泄漏? - Delphi: Why does this generate a memory leak? 为什么SQLyog返回的MySQL查询结果比我的Delphi程序快10倍? - Why does SQLyog returns MySQL query results 10x faster than my Delphi program? 为什么在Delphi 2007 for Win32中使用StrtoFloat()时,我的程序为什么仍会引发EConvertError? - Why does my program keep raising EConvertError when using StrtoFloat() in Delphi 2007 for Win32? 为什么我释放内存后程序的内存使用率没有恢复正常? - Why doesn't my program's memory usage return to normal after I free memory? 为什么我的程序泄漏虚拟内存? - Why is my program leaking Virtual Memory? 为什么SetString在Delphi中使用较少的内存(使用Unicode)? - Why Does SetString Take Less Memory in Delphi (with Unicode)? 使用Delphi 7,为什么一个简单的列表框会增加内存 - Using Delphi 7 why does a simple listbox give memory buildup
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM