2009-11-05 146 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