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