堆碎片和Windows内存管理器

hjqgdpho  于 2022-11-26  发布在  Windows
关注(0)|答案(5)|浏览(216)

我遇到了内存碎片的问题,我的程序不能分配很大的内存块。我已经看了这个论坛上的相关帖子-主要是this。我仍然有一些问题。
我一直在使用内存空间profiler来获取内存的图片。我写了一个1行程序,包含cin〉〉var;拍了一张记忆的照片

在上面的弧线-绿色表示空白空间,黄色表示已分配,红色表示已提交。我的问题是右边分配的内存是什么?是主线程的堆栈吗?这个内存不会被释放,它会分割我需要的连续内存。在这个简单的1行程序中,分割并不那么糟糕。我的实际程序在地址空间的中间分配了更多的东西。我也不知道它是从哪里来的我还没分配内存
1.我该如何解决这个问题呢?我想改用nedmalloc或dlmalloc之类的方法。然而,这只适用于我自己显式分配的对象,而图片中显示的拆分不会消失?或者有没有办法用另一个内存管理器来替换CRT分配?
1.说到对象,有没有nedmalloc for c++的 Package 器,这样我就可以用new和delete来分配对象了?

ru9i0ody

ru9i0ody1#

首先,感谢您使用我的工具。我希望您觉得它有用,并随时提交功能请求或贡献。
通常,地址空间中固定点的薄切片是由链接的dll在其首选地址加载引起的。在地址空间中加载较高位置的往往是Microsoft操作系统dll。如果这些dll都可以在其首选地址加载,则对操作系统来说效率更高,因为这样dll的只读部分就可以在进程之间共享。
您可以看到的切片没有什么好担心的,它几乎不从地址空间中剪切任何内容。正如您所注意到的,尽管有一些dll在地址空间的其他位置加载。IIRC shlwapi.dll是一个特别糟糕的例子。加载约0x 2000000(又是IIRC),它经常将大部分可用地址空间分成两个较小的部分。这样做的问题是一旦DLL被加载,您无法移动此分配的空间。
如果你链接到DLL(直接或通过另一个DLL),你什么都做不了。如果你使用LoadLibrary,你可以偷偷摸摸地保留它的首选地址,迫使它在释放保留的内存之前被重新定位--通常是在地址空间中更好的地方。但这并不总是有效。
实际上,地址空间监视器使用VirtualQueryEx来检查进程的地址空间,但还有来自psapi库的另一个调用,其他工具使用该调用(例如Process Explorer),它可以显示哪些文件(包括DLL)Map到地址空间的哪些部分。
正如您所发现的,在2GB的用户地址空间中,空间非常容易用完。从根本上说,防止内存碎片的最佳防御措施就是不需要任何大的连续内存块。虽然很难进行改造,但将应用程序设计为使用“中等大小”的内存块通常会大大提高地址空间的使用效率。
同样,您可以使用分页策略,可能使用内存Map文件或Address Windowing Extensions

gywdnpxw

gywdnpxw2#

我假设您经常分配和释放不同大小的对象,这就是导致内存碎片问题的原因?
有各种策略可以解决这些问题;你提到的不同的内存管理器可能会帮助你解决碎片问题,但是这需要对碎片的根本原因进行更多的分析。2例如,如果你经常分配三到四种类型的对象,而这往往会使内存碎片问题恶化,您可能希望将这些块放入它们自己的内存池中以便重用正确大小的内存块,你应该有一组内存块来适应这个特定的对象,并防止出现这样的情况:对象X的分配分割了一个足够大的内存块来容纳Y,这样你就突然不能“I don“我不再分配任何Y了。
至于(2),我不知道nedmalloc周围有一个 Package 器(坦白地说,我对nedmalloc不是很熟悉)但是你可以很容易地创建你自己的 Package 器,因为你可以创建特定于类的操作符new和delete,甚至可以重载/替换全局操作符new和delete。我不是后者的忠实拥护者,但是如果你的分配“热点”由一些类组成,通常很容易用它们自己的、特定于类的操作符new和delete对其进行改进。
也就是说,nedmalloc声称自己是标准malloc/free的替代品,至少在MS编译器中是这样,我认为C++运行时库会将new/delete转发到malloc/free,所以这很可能只是用nedmalloc构建可执行文件的一个例子。

nzkunb0c

nzkunb0c3#

为了减少内存碎片,你可以利用Windows Low-Fragmentation Heap。我们在产品中使用了它,效果很好,自从这样做以来,几乎没有出现过那么多与内存有关的问题。

ej83mcc0

ej83mcc04#

可能是可执行文件吗?它必须被加载到某个地址空间中....
至于2,它很容易覆盖全局新建和删除函数...只需定义它们。

tzcvj98z

tzcvj98z5#

找出程序中内存分配位置的最好方法是使用调试器。每个加载的DLL和可执行文件本身都有分配,它们都将虚拟内存分成碎片。另外,使用C/C++库和Windows API将导致在应用程序中创建堆,这至少会保留一块虚拟内存。
例如,您可以使用VirtualAlloc在一个相对较小的程序中保留一大块虚拟内存,但却发现VirtualAlloc失败,或者应用程序稍后在尝试加载新DLL时失败(等等)你也不能总是控制什么DLL将被加载和加载到哪里。许多A/V和其他产品会在所有运行的进程启动时将DLL注入到它们中。当这种情况发生时,这些DLL通常首先选择加载地址--也就是说,默认情况下它们的编译/链接地址可能会被授予。在典型的32位Windows应用程序的可用2GB虚拟地址空间之外,如果DLL恰好在该地址空间的中间加载,则您可以获得的最大单次分配/保留将小于1GB。
如果使用windbg,可以看到哪些内存区域被消耗、保留等等。lm命令将显示所有DLL和EXE的加载地址及其范围。!vadump命令将显示进程和页面保护所使用的所有虚拟内存。页面保护是一个很大的提示。例如(partial)!vadump,您会看到第一个区域只是一个受保护而无法访问的虚拟内存范围。MEM_COMMIT意味着内存由RAM或分页文件支持。PAGE_READWRITE可能是堆内存,或者是已加载模块的数据段。PAGE_READEXECUTE通常是已加载的代码,它将显示在lm生成的列表中。MEM_RESERVE表示调用了VirtualAlloc以保留内存区域,但它没有被虚拟内存管理器Map,等等...

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是一个很棒的工具,有很多扩展可以帮助你找到内存的使用位置。
如果你真的只关心堆,看看!堆。

相关问题