我的程序中存在内存碎片问题,一段时间后无法分配非常大的内存块。我已经阅读了这个论坛上的相关帖子 - 主要是this一个。我还有一些问题。
我一直在使用内存空间profiler来获取内存的图片。我写了一个包含cin>>的1行程序。 VAR;并拍下了记忆的照片:
alt text http://img22.imageshack.us/img22/6808/memoryk.gif 顶部弧形的位置 - 绿色表示空白,黄色分配,红色表示。我的问题是右侧分配的内存是什么?它是主线程的堆栈吗?这个内存不会被释放,它会分裂我需要的连续内存。在这个简单的1行程序中,拆分并不是那么糟糕。我的实际程序在地址空间的中间分配了更多的东西,我不知道它来自哪里。我还没有分配那个记忆。
我该如何解决这个问题?我想改用像nedmalloc或dlmalloc这样的东西。但是,这只适用于我自己明确分配的对象,而图片中显示的分割不会消失?或者有没有办法用另一个内存管理器替换CRT分配?
说到对象,c ++的nedmalloc是否有任何包装器,所以我可以使用new和delete来分配对象?
感谢。
答案 0 :(得分:12)
首先,感谢您使用我的工具。我希望您觉得它很有用,并随时提交功能请求或贡献。
通常,地址空间中固定点处的薄片是由链接的dll加载到其首选地址引起的。在地址空间中加载高的那些往往是Microsoft操作系统dll。如果操作系统可以全部加载到它们的首选地址,那么它对于操作系统来说效率会更高,因为dll的只读部分都可以在进程之间共享。
您可以看到的切片无需担心,它几乎不会削减任何地址空间。正如您所指出的那样,有些dll会加载到地址空间的其他位置。 IIRC shlwapi.dll
是一个特别糟糕的例子,加载大约0x2000000(再次是IIRC),它经常将大部分可用地址空间分成两个较小的部分。这个问题是,一旦加载了DLL,你就无法移动这个分配空间。
如果你链接DLL(直接或通过另一个DLL),你无能为力。如果使用LoadLibrary
,您可以偷偷摸摸地保留其首选地址,强制将其重新定位 - 通常在地址空间中更好的地方 - 然后释放该保留的内存。但这并不总是有效。
引擎盖下,地址空间监视器使用VirtualQueryEx
来检查进程的地址空间,但还有来自psapi库的另一个调用,其他工具使用(例如Process Explorer)可以显示哪个文件(包括DLL)映射到地址空间的哪些部分。
正如您所发现的,在2GB用户地址空间中用完房间可能非常容易。从根本上说,你最好的防御内存碎片只是不需要任何大的连续内存块。虽然难以改装,但设计应用程序以使用“中型”块通常可以更有效地使用地址空间。
同样,您可以使用分页策略,可能使用内存映射文件或Address Windowing Extensions。
答案 1 :(得分:2)
我假设您经常分配和解除分配不同大小的对象,这是导致内存碎片问题的原因吗?
有各种策略来解决这些问题;如果他们可以为您解决碎片问题,那么您提到的不同内存管理器可能会有所帮助,但这需要对碎片的根本原因进行更多分析。例如,如果经常分配三种或四种类型的对象,这些往往会加剧内存碎片问题,您可能希望将这些对象放入自己的内存池中,以便重用正确大小的内存块。这样,你应该有一组适合这个特定对象的内存块,并防止常见的情况是对象X的分配拆分了一个大到足以容纳Y的内存块,使你突然无法分配任何Y了。
对于(2),我不知道nedmalloc的包装器(坦率地说,我对nedmalloc不是很熟悉)但你可以很容易地创建自己的包装器,因为你可以创建特定于类的运算符new并删除甚至重载/替换全局运算符new和delete。我不是后者的忠实粉丝,但如果你的分配“热点”由少数几个类组成,通常很容易用自己的,特定于类的运算符new和delete来改进它们。
那就是说,nedmalloc自称是标准malloc / free的替代品,至少对MS编译器来说,我认为C ++运行时库会将new / delete转发给malloc / free,所以很可能只是一个例子。使用nedmalloc构建可执行文件。
答案 2 :(得分:2)
为了减少内存碎片,您可以利用Windows Low-Fragmentation Heap。我们已经在我们的产品中使用了这个效果,并且因为这样做而没有几乎与内存相关的问题。
答案 3 :(得分:1)
它可以是可执行文件吗?它必须加载到某个地址空间....
至于2,它很容易覆盖全局的新的和删除函数......只需定义它们。
答案 4 :(得分:1)
找出程序中内存分配位置的最佳方法是使用调试器。每个加载的DLL和可执行文件本身都有分配,所有这些都分段虚拟内存。此外,使用C / C ++库和Windows API将导致在您的应用程序中创建堆,这至少会保留一大块虚拟内存。
例如,您可以使用VirtualAlloc在相对较小的程序中保留大量虚拟内存,但只发现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是一个很棒的工具,有很多扩展可以帮助你找到使用内存的地方。
如果你真的关心堆,请看看!堆。