2009-11-05 102 views
9

我在我的程序中遇到了内存碎片问题,并且一段时间后无法分配非常大的内存块。我已阅读此论坛上的相关帖子 - 主要是this之一。我还有一些问题。堆碎片和Windows内存管理器

我一直在使用内存空间profiler来获取内存的图片。我写了一个包含cin >> var;并拍下了内存的图片:

alt text http://img22.imageshack.us/img22/6808/memoryk.gif 顶部圆弧的位置 - 绿色表示空白,黄色分配,红色提交。我的问题是右边分配的内存是什么?它是主线程的堆栈吗?这个内存不会被释放,它会分割我需要的连续内存。在这个简单的1行程序中,拆分并不是很糟糕。我的实际程序在地址空间中间分配了更多东西,我不知道它来自哪里。我还没有分配那个内存。

  1. 我该如何解决这个问题?我正在考虑切换到像nedmalloc或dlmalloc这样的东西。但是,这只适用于我自己明确分配的对象,而图片中显示的分割不会消失?或者有没有办法用另一个内存管理器替换CRT分配?

  2. 说到对象,是否有nedmalloc for C++的任何包装,所以我可以使用new和delete来分配对象?

谢谢。

+1

Microsoft Security Essentials认为原始问题中链接的“探查器”应用程序包含Win32.Bisar!rts木马。 – 2012-03-13 20:43:05

回答

12

首先,感谢您使用我的工具。我希望你觉得它有用并且随意提交功能请求或贡献。

通常情况下,地址空间中固定点的薄片是由链接的dll在其首选地址加载引起的。在地址空间中加载高的往往是Microsoft操作系统dll。如果这些操作系统都可以加载到它们的首选地址,那么对于操作系统来说效率更高,因为这样dll的只读部分都可以在进程之间共享。

您可以看到的切片无需担心,它几乎不会从地址空间中删除任何内容。正如你所指出的那样,尽管存在dll,但是在地址空间中的其他点加载。 IIRC shlwapi.dll是一个特别糟糕的例子,加载约为0x2000000(又是IIRC),经常会将大部分可用地址空间拆分为两个较小的块。这个问题是,一旦加载DLL,就没有办法移动这个分配空间。

如果您链接到DLL(直接或通过另一个DLL),则没有任何可以执行的操作。如果你使用LoadLibrary,你可以偷偷摸摸地保留它的首选地址,并在释放保留的内存之前强制它重新定位 - 经常在地址空间中更好的位置。但这并不总是奏效。

在引擎盖下,地址空间监视器使用VirtualQueryEx来检查进程的地址空间,但还有其他工具使用的另一个来自psapi库的调用(例如Process Explorer),它可以显示映射哪些文件(包括DLL)进入哪些地址空间的哪些部分。

正如您发现的那样,在2GB用户地址空间内用完空间可能会非常容易。从根本上说,最好的防御内存碎片就是不需要任何大的连续内存块。虽然很难进行改造,但设计应用程序以使用“中等大小”块通常可以更有效地使用地址空间。

同样,您可以使用分页策略,可能使用内存映射文件或Address Windowing Extensions

+0

嘿,感谢一个伟大的工具,并指出该过程资源管理器的功能。 – Budric 2009-11-06 15:41:07

+0

@Charles Bailey:重新静态链接 - 无法重新构建DLL? – rpg 2009-11-06 15:50:18

+0

是的,你可以重新绑定DLL,而这*可以*帮助优化地址空间碎片和加载时间,但是......通常你只应该用你拥有的DLL来做到这一点,如果你做了太多的微操作,一组DLL在一个特定的exe文件中在某个时间点很好地工作,但对于其他任何exe文件都没有这么好的优化加载策略。如果你的DLL被重新构建并且改变了大小,那么你必须再次完成这个过程。所以它可以在某种程度上起作用,但是如果你不得不求助于它,那么你可能会陷入高维护的恶性循环。 – 2009-11-06 17:37:45

1

难道是可执行文件吗?它必须被加载到地址空间...

至于2它很容易覆盖全局的新功能和删除功能......只是定义它们。

2

我认为你经常分配和释放不同大小的对象,这是什么导致你的内存碎片问题?

有很多策略可以解决这些问题;你提到的不同的内存管理者可能会帮助你解决碎片问题,但这需要对碎片的根本原因进行更多的分析。例如,如果您经常分配三种或四种类型的对象,而这些往往会恶化内存碎片问题,您可能希望将这些对象放入其自己的内存池中,以便重新使用正确大小的内存块。这样,你应该有一组适合这个特定对象的内存块,并且避免了这样一种情况:分配对象X的内存块分割的内存块足够大,可以容纳Y,使得你突然无法分配任何Y了。 (2),我不知道nedmalloc的包装(坦率地说,我对nedmalloc不是很熟悉),但是你可以很容易地创建自己的包装,因为你可以创建特定于类的操作符新的和删除甚至超载/替换全球运营商的新增和删除。我不是后者的忠实粉丝,但是如果你的配置“热点”由少数类组成,通常很容易用他们自己的,特定于类的运算符进行新的和删除。

也就是说,nedmalloc声称自己是标准malloc/free的替代品,至少在MS编译器中,我认为C++运行时库会将new/delete转发给malloc/free,所以它可能只是一个例子用nedmalloc构建你的可执行文件。

1

找出程序中分配内存的最佳方法是使用调试器。每个加载的DLL和可执行文件本身都有分配,并且它们全部分割虚拟内存。另外,使用C/C++库和Windows API将会在您的应用程序中创建一个堆,至少会保留一大块虚拟内存。

例如,你可以使用VirtualAlloc储备了大量chunck虚拟内存在一个相对较小的项目,才发现,无论是的VirtualAlloc失败,或者当它尝试加载一个新的DLL应用程序后失败(等)你也不能总是控制哪些DLL将被加载和在哪里。许多A/V和其他产品将在开始时将DLL注入到所有正在运行的进程中。发生这种情况时,这些DLL通常会在加载地址上进行首次挑选 - 也就是说,它们的默认编译/链接可能会被授予。在典型的32位Windows应用程序的可用2GB虚拟地址空间之外,如果DLL在该地址空间的中间加载,则可以获得的最大单个分配/预留将少于1 GB。

如果你使用windbg,你可以看到哪些区域的内存被占用,保留等等.lm命令将显示所有DLL的负载地址以及EXE及其范围。 !vadump命令将显示进程使用的所有虚拟内存和页面保护。页面保护是对那里有什么的重要暗示。例如,在来自64位calc.exe进程的以下(部分)!vadump中,您将看到第一个区域只是一系列受保护的虚拟内存。 (除此之外,这使您无法在地址0处分配内存。)MEM_COMMIT表示内存由RAM或分页文件支持。 PAGE_READWRITE可能是堆内存或加载模块的数据段。 PAGE_READEXECUTE通常是加载的代码,并且会显示在由lm生成的列表中。 MEM_RESERVE意味着什么呼吁的VirtualAlloc预留的内存区域,但它不是由虚拟内存管理器映射,等等......

0:004> !vadump 
BaseAddress:  0000000000000000 
RegionSize:  0000000000010000 
State:    00010000 MEM_FREE 
Protect:   00000001 PAGE_NOACCESS 

BaseAddress:  0000000000010000 
RegionSize:  0000000000010000 
State:    00001000 MEM_COMMIT 
Protect:   00000004 PAGE_READWRITE 
Type:    00040000 MEM_MAPPED 

BaseAddress:  0000000000020000 
RegionSize:  0000000000003000 
State:    00001000 MEM_COMMIT 
Protect:   00000002 PAGE_READONLY 
Type:    00040000 MEM_MAPPED 

我希望帮助解释的事情。 Windbg是一款出色的工具,它有很多扩展功能可以帮助您找到使用内存的地方。

如果你真的只关心堆,看看堆!

+0

谢谢,我会试用windbg。 – Budric 2009-11-05 23:03:12

2

为了减少内存碎片,您可以利用Windows Low-Fragmentation Heap。我们已经在我们的产品中使用了这个功能,效果很好,并且因为这样做而没有遇到过与内存相关的问题。

+0

我看过这个功能。但是为了启用它,你必须运行HeapSetInformation()。内存快照是在main()的第一行进行的,并且在第一个1.3 GB的地址空间之后内存已经被分割了。在进程浏览器中查看它之后,它就是DLL和其他东西。所以LFH可能会有所帮助,但它不会阻止由于DLL加载而发生的碎片。 – Budric 2009-11-10 22:11:14