原文:https://www.ibm.com/developerworks/cn/linux/l-memory/html
內存管理是計算機編程最爲基本的領域之一。在不少腳本語言中,您沒必要擔憂內存是如何管理的,這並不能使得內存管理的重要性有一點點下降。對實際編程來講,理解您的內存管理器的能力與侷限性相當重要。在大部分系統語言中,好比 C 和 C++,您必須進行內存管理。本文將介紹手工的、半手工的以及自動的內存管理實踐的基本概念。node
追溯到在 Apple II 上進行彙編語言編程的時代,那時內存管理還不是個大問題。您實際上在運行整個系統。系統有多少內存,您就有多少內存。您甚至沒必要費心思去弄明白它有多少內存,由於每一臺機器的內存數量都相同。因此,若是內存須要很是固定,那麼您只須要選擇一個內存範圍並使用它便可。linux
不過,即便是在這樣一個簡單的計算機中,您也會有問題,尤爲是當您不知道程序的每一個部分將須要多少內存時。若是您的空間有限,而內存需求是變化的,那麼您須要一些方法來知足這些需求:程序員
實現這些需求的程序庫稱爲 分配程序(allocators),由於它們負責分配和回收內存。程序的動態性越強,內存管理就越重要,您的內存分配程序的選擇也就更重要。讓咱們來了解可用於內存管理的不一樣方法,它們的好處與不足,以及它們最適用的情形。web
回頁首算法
C 編程語言提供了兩個函數來知足咱們的三個需求:apache
malloc
分配的內存片斷的指針,並將其釋放,以便之後的程序或操做系統使用(實際上,一些 malloc
實現只能將內存歸還給程序,而沒法將內存歸還給操做系統)。要理解內存在程序中是如何分配的,首先須要理解如何將內存從操做系統分配給程序。計算機上的每個進程都認爲本身能夠訪問全部的物理內存。顯然,因爲同時在運行多個程序,因此每一個進程不可能擁有所有內存。實際上,這些進程使用的是 虛擬內存。
只是做爲一個例子,讓咱們假定您的程序正在訪問地址爲 629 的內存。不過,虛擬內存系統不須要將其存儲在位置爲 629 的 RAM 中。實際上,它甚至能夠不在 RAM 中 —— 若是物理 RAM 已經滿了,它甚至可能已經被轉移到硬盤上!因爲這類地址沒必要反映內存所在的物理位置,因此它們被稱爲虛擬內存。操做系統維持着一個虛擬地址到物理地址的轉換的表,以便計算機硬件能夠正確地響應地址請求。而且,若是地址在硬盤上而不是在 RAM 中,那麼操做系統將暫時中止您的進程,將其餘內存轉存到硬盤中,從硬盤上加載被請求的內存,而後再從新啓動您的進程。這樣,每一個進程都得到了本身可使用的地址空間,能夠訪問比您物理上安裝的內存更多的內存。
在 32-位 x86 系統上,每個進程能夠訪問 4 GB 內存。如今,大部分人的系統上並無 4 GB 內存,即便您將 swap 也算上, 每一個進程所使用的內存也確定少於 4 GB。所以,當加載一個進程時,它會獲得一個取決於某個稱爲 系統中斷點(system break)的特定地址的初始內存分配。該地址以後是未被映射的內存 —— 用於在 RAM 或者硬盤中沒有分配相應物理位置的內存。所以,若是一個進程運行超出了它初始分配的內存,那麼它必須請求操做系統「映射進來(map in)」更多的內存。(映射是一個表示一一對應關係的數學術語 —— 當內存的虛擬地址有一個對應的物理地址來存儲內存內容時,該內存將被映射。)
基於 UNIX 的系統有兩個可映射到附加內存中的基本系統調用:
brk()
是一個很是簡單的系統調用。還記得系統中斷點嗎?該位置是進程映射的內存邊界。 brk()
只是簡單地將這個位置向前或者向後移動,就能夠向進程添加內存或者從進程取走內存。mmap()
,或者說是「內存映像」,相似於 brk()
,可是更爲靈活。首先,它能夠映射任何位置的內存,而不僅僅只侷限於進程。其次,它不只能夠將虛擬地址映射到物理的 RAM 或者 swap,它還能夠將它們映射到文件和文件位置,這樣,讀寫內存將對文件中的數據進行讀寫。不過,在這裏,咱們只關心 mmap
向進程添加被映射的內存的能力。 munmap()
所作的事情與 mmap()
相反。如您所見, brk()
或者 mmap()
均可以用來向咱們的進程添加額外的虛擬內存。在咱們的例子中將使用 brk()
,由於它更簡單,更通用。
若是您曾經編寫過不少 C 程序,那麼您可能曾屢次使用過 malloc()
和 free()
。不過,您可能沒有用一些時間去思考它們在您的操做系統中是如何實現的。本節將向您展現 malloc
和 free
的一個最簡化實現的代碼,來幫助說明管理內存時都涉及到了哪些事情。
要試着運行這些示例,須要先 複製本代碼清單,並將其粘貼到一個名爲 malloc.c 的文件中。接下來,我將一次一個部分地對該清單進行解釋。
在大部分操做系統中,內存分配由如下兩個簡單的函數來處理:
void *malloc(long numbytes)
:該函數負責分配 numbytes
大小的內存,並返回指向第一個字節的指針。void free(void *firstbyte)
:若是給定一個由先前的 malloc
返回的指針,那麼該函數會將分配的空間歸還給進程的「空閒空間」。malloc_init
將是初始化內存分配程序的函數。它要完成如下三件事:將分配程序標識爲已經初始化,找到系統中最後一個有效內存地址,而後創建起指向咱們管理的內存的指針。這三個變量都是全局變量:
int has_initialized = 0; void *managed_memory_start; void *last_valid_address; |
如前所述,被映射的內存的邊界(最後一個有效地址)常被稱爲系統中斷點或者 當前中斷點。在不少 UNIX® 系統中,爲了指出當前系統中斷點,必須使用 sbrk(0)
函數。 sbrk
根據參數中給出的字節數移動當前系統中斷點,而後返回新的系統中斷點。使用參數 0
只是返回當前中斷點。這裏是咱們的 malloc
初始化代碼,它將找到當前中斷點並初始化咱們的變量:
/* Include the sbrk function */ #include <unistd.h> void malloc_init() { /* grab the last valid address from the OS */ last_valid_address = sbrk(0); /* we don't have any memory to manage yet, so *just set the beginning to be last_valid_address */ managed_memory_start = last_valid_address; /* Okay, we're initialized and ready to go */ has_initialized = 1; } |
如今,爲了徹底地管理內存,咱們須要可以追蹤要分配和回收哪些內存。在對內存塊進行了 free
調用以後,咱們須要作的是諸如將它們標記爲未被使用的等事情,而且,在調用 malloc
時,咱們要可以定位未被使用的內存塊。所以, malloc
返回的每塊內存的起始處首先要有這個結構:
struct mem_control_block { int is_available; int size; }; |
如今,您可能會認爲當程序調用 malloc
時這會引起問題 —— 它們如何知道這個結構?答案是它們沒必要知道;在返回指針以前,咱們會將其移動到這個結構以後,把它隱藏起來。這使得返回的指針指向沒有用於任何其餘用途的內存。那樣,從調用程序的角度來看,它們所獲得的所有是空閒的、開放的內存。而後,當經過 free()
將該指針傳遞回來時,咱們只須要倒退幾個內存字節就能夠再次找到這個結構。
在討論分配內存以前,咱們將先討論釋放,由於它更簡單。爲了釋放內存,咱們必需要作的唯一一件事情就是,得到咱們給出的指針,回退 sizeof(struct mem_control_block)
個字節,並將其標記爲可用的。這裏是對應的代碼:
void free(void *firstbyte) { struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block */ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That's It! We're done. */ return; } |
如您所見,在這個分配程序中,內存的釋放使用了一個很是簡單的機制,在固定時間內完成內存釋放。分配內存稍微困難一些。如下是該算法的略述:
1. If our allocator has not been initialized, initialize it. 2. Add sizeof(struct mem_control_block) to the size requested. 3. start at managed_memory_start. 4. Are we at last_valid address? 5. If we are: A. We didn't find any existing space that was large enough -- ask the operating system for more and return that. 6. Otherwise: A. Is the current space available (check is_available from the mem_control_block)? B. If it is: i) Is it large enough (check "size" from the mem_control_block)? ii) If so: a. Mark it as unavailable b. Move past mem_control_block and return the pointer iii) Otherwise: a. Move forward "size" bytes b. Go back go step 4 C. Otherwise: i) Move forward "size" bytes ii) Go back to step 4 |
咱們主要使用鏈接的指針遍歷內存來尋找開放的內存塊。這裏是代碼:
void *malloc(long numbytes) { /* Holds where we are looking in memory */ void *current_location; /* This is the same as current_location, but cast to a * memory_control_block */ struct mem_control_block *current_location_mcb; /* This is the memory location we will return. It will * be set to 0 until we find something suitable */ void *memory_location; /* Initialize if we haven't already done so */ if(! has_initialized) { malloc_init(); } /* The memory we search for has to include the memory * control block, but the users of malloc don't need * to know this, so we'll just add it in for them. */ numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable * location */ memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_start; /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) { /* current_location and current_location_mcb point * to the same address. However, current_location_mcb * is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses. */ current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available) { if(current_location_mcb->size >= numbytes) { /* Woohoo! We've found an open, * appropriately-size location. */ /* It is no longer available */ current_location_mcb->is_available = 0; /* We own it */ memory_location = current_location; /* Leave the loop */ break; } } /* If we made it here, it's because the Current memory * block not suitable; move to the next one */ current_location = current_location + current_location_mcb->size; } /* If we still don't have a valid location, we'll * have to ask the operating system for more memory */ if(! memory_location) { /* Move the program break numbytes further */ sbrk(numbytes); /* The new memory will be where the last valid * address left off */ memory_location = last_valid_address; /* We'll move the last valid address forward * numbytes */ last_valid_address = last_valid_address + numbytes; /* We need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; } /* Now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block */ /* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_location; } |
這就是咱們的內存管理器。如今,咱們只須要構建它,並在程序中使用它便可。
運行下面的命令來構建 malloc
兼容的分配程序(實際上,咱們忽略了 realloc()
等一些函數,不過, malloc()
和 free()
纔是最主要的函數):
gcc -shared -fpic malloc.c -o malloc.so |
該程序將生成一個名爲 malloc.so 的文件,它是一個包含有咱們的代碼的共享庫。
在 UNIX 系統中,如今您能夠用您的分配程序來取代系統的 malloc()
,作法以下:
LD_PRELOAD=/path/to/malloc.so export LD_PRELOAD |
LD_PRELOAD
環境變量使動態連接器在加載任何可執行程序以前,先加載給定的共享庫的符號。它還爲特定庫中的符號賦予優先權。所以,從如今起,該會話中的任何應用程序都將使用咱們的 malloc()
,而不是隻有系統的應用程序可以使用。有一些應用程序不使用 malloc()
,不過它們是例外。其餘使用 realloc()
等其餘內存管理函數的應用程序,或者錯誤地假定 malloc()
內部行爲的那些應用程序,極可能會崩潰。ash shell 彷佛可使用咱們的新 malloc()
很好地工做。
若是您想確保 malloc()
正在被使用,那麼您應該經過向函數的入口點添加 write()
調用來進行測試。
咱們的內存管理器在不少方面都還存在欠缺,但它能夠有效地展現內存管理須要作什麼事情。它的某些缺點包括:
mmap
一塊兒使用。malloc
只假定內存分配是成功的)。realloc()
。sbrk()
可能會交回比咱們請求的更多的內存,因此在堆(heap)的末端會遺漏一些內存。is_available
標記只包含一位信息,但它要使用完整的 4-字節 的字。malloc()
的實現有不少,這些實現各有優勢與缺點。在設計一個分配程序時,要面臨許多須要折衷的選擇,其中包括:
每個實現都有其自身的優缺點集合。在咱們的簡單的分配程序中,分配很是慢,而回收很是快。另外,因爲它在使用虛擬內存系統方面較差,因此它最適於處理大的對象。
還有其餘許多分配程序可使用。其中包括:
ptmalloc
。 Doug Lea 的分配程序有着與咱們的版本很是相似的基本結構,可是它加入了索引,這使得搜索速度更快,而且能夠將多個沒有被使用的塊組合爲一個大的塊。它還支持緩存,以便更快地再次使用最近釋放的內存。 ptmalloc
是 Doug Lea Malloc 的一個擴展版本,支持多線程。在本文後面的 參考資料部分中,有一篇描述 Doug Lea 的 Malloc 實現的文章。衆多可用的分配程序中最有名的就是上述這些分配程序。若是您的程序有特別的分配需求,那麼您可能更願意編寫一個定製的能匹配您的程序內存分配方式的分配程序。不過,若是不熟悉分配程序的設計,那麼定製分配程序一般會帶來比它們解決的問題更多的問題。要得到關於該主題的適當的介紹,請參閱 Donald Knuth 撰寫的 The Art of Computer Programming Volume 1: Fundamental Algorithms 中的第 2.5 節「Dynamic Storage Allocation」(請參閱 參考資料中的連接)。它有點過期,由於它沒有考慮虛擬內存環境,不過大部分算法都是基於前面給出的函數。
在 C++ 中,經過重載 operator new()
,您能夠以每一個類或者每一個模板爲單位實現本身的分配程序。在 Andrei Alexandrescu 撰寫的 Modern C++ Design 的第 4 章(「Small Object Allocation」)中,描述了一個小對象分配程序(請參閱 參考資料中的連接)。
不僅是咱們的內存管理器有缺點,基於 malloc()
的內存管理器仍然也有不少缺點,無論您使用的是哪一個分配程序。對於那些須要保持長期存儲的程序使用 malloc()
來管理內存可能會很是使人失望。若是您有大量的不固定的內存引用,常常難以知道它們什麼時候被釋放。生存期侷限於當前函數的內存很是容易管理,可是對於生存期超出該範圍的內存來講,管理內存則困可貴多。並且,關於內存管理是由進行調用的程序仍是由被調用的函數來負責這一問題,不少 API 都不是很明確。
由於管理內存的問題,不少程序傾向於使用它們本身的內存管理規則。C++ 的異常處理使得這項任務更成問題。有時好像致力於管理內存分配和清理的代碼比實際完成計算任務的代碼還要多!所以,咱們將研究內存管理的其餘選擇。
引用計數是一種 半自動(semi-automated)的內存管理技術,這表示它須要一些編程支持,可是它不須要您確切知道某一對象什麼時候再也不被使用。引用計數機制爲您完成內存管理任務。
在引用計數中,全部共享的數據結構都有一個域來包含當前活動「引用」結構的次數。當向一個程序傳遞一個指向某個數據結構指針時,該程序會將引用計數增長 1。實質上,您是在告訴數據結構,它正在被存儲在多少個位置上。而後,當您的進程完成對它的使用後,該程序就會將引用計數減小 1。結束這個動做以後,它還會檢查計數是否已經減到零。若是是,那麼它將釋放內存。
這樣作的好處是,您沒必要追蹤程序中某個給定的數據結構可能會遵循的每一條路徑。每次對其局部的引用,都將致使計數的適當增長或減小。這樣能夠防止在使用數據結構時釋放該結構。不過,當您使用某個採用引用計數的數據結構時,您必須記得運行引用計數函數。另外,內置函數和第三方的庫不會知道或者可使用您的引用計數機制。引用計數也難以處理髮生循環引用的數據結構。
要實現引用計數,您只須要兩個函數 —— 一個增長引用計數,一個減小引用計數並當計數減小到零時釋放內存。
一個示例引用計數函數集可能看起來以下所示:
/* Structure Definitions*/ /* Base structure that holds a refcount */ struct refcountedstruct { int refcount; } /* All refcounted structures must mirror struct * refcountedstruct for their first variables */ /* Refcount maintenance functions */ /* Increase reference count */ void REF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount++; } /* Decrease reference count */ void UNREF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount--; /* Free the structure if there are no more users */ if(rstruct->refcount == 0) { free(rstruct); } } |
REF
和 UNREF
可能會更復雜,這取決於您想要作的事情。例如,您可能想要爲多線程程序增長鎖,那麼您可能想擴展 refcountedstruct
,使它一樣包含一個指向某個在釋放內存以前要調用的函數的指針(相似於面嚮對象語言中的析構函數 —— 若是您的結構中包含這些指針,那麼這是 必需的)。
當使用 REF
和 UNREF
時,您須要遵照這些指針的分配規則:
UNREF
分配前左端指針(left-hand-side pointer)指向的值。REF
分配後左端指針(left-hand-side pointer)指向的值。在傳遞使用引用計數的結構的函數中,函數須要遵循如下這些規則:
如下是一個使用引用計數的生動的代碼示例:
/* EXAMPLES OF USAGE */ /* Data type to be refcounted */ struct mydata { int refcount; /* same as refcountedstruct */ int datafield1; /* Fields specific to this struct */ int datafield2; /* other declarations would go here as appropriate */ }; /* Use the functions in code */ void dosomething(struct mydata *data) { REF(data); /* Process data */ /* when we are through */ UNREF(data); } struct mydata *globalvar1; /* Note that in this one, we don't decrease the * refcount since we are maintaining the reference * past the end of the function call through the * global variable */ void storesomething(struct mydata *data) { REF(data); /* passed as a parameter */ globalvar1 = data; REF(data); /* ref because of Assignment */ UNREF(data); /* Function finished */ } |
因爲引用計數是如此簡單,大部分程序員都自已去實現它,而不是使用庫。不過,它們依賴於 malloc
和 free
等低層的分配程序來實際地分配和釋放它們的內存。
在 Perl 等高級語言中,進行內存管理時使用引用計數很是普遍。在這些語言中,引用計數由語言自動地處理,因此您根本沒必要擔憂它,除非要編寫擴展模塊。因爲全部內容都必須進行引用計數,因此這會對速度產生一些影響,但它極大地提升了編程的安全性和方便性。如下是引用計數的益處:
不過,它也有其不足之處:
try
或 setjmp()
/ longjmp()
)時,您必須採起其餘方法。C++ 能夠經過使用 智能指針(smart pointers)來容忍程序員所犯的一些錯誤,智能指針能夠爲您處理引用計數等指針處理細節。不過,若是不得不使用任何先前的不能處理智能指針的代碼(好比對 C 庫的聯接),實際上,使用它們的後果通實比不使用它們更爲困難和複雜。所以,它一般只是有益於純 C++ 項目。若是您想使用智能指針,那麼您實在應該去閱讀 Alexandrescu 撰寫的 Modern C++ Design 一書中的「Smart Pointers」那一章。
內存池是另外一種半自動內存管理方法。內存池幫助某些程序進行自動內存管理,這些程序會經歷一些特定的階段,並且每一個階段中都有分配給進程的特定階段的內存。例如,不少網絡服務器進程都會分配不少針對每一個鏈接的內存 —— 內存的最大生存期限爲當前鏈接的存在期。Apache 使用了池式內存(pooled memory),將其鏈接拆分爲各個階段,每一個階段都有本身的內存池。在結束每一個階段時,會一次釋放全部內存。
在池式內存管理中,每次內存分配都會指定內存池,從中分配內存。每一個內存池都有不一樣的生存期限。在 Apache 中,有一個持續時間爲服務器存在期的內存池,還有一個持續時間爲鏈接的存在期的內存池,以及一個持續時間爲請求的存在期的池,另外還有其餘一些內存池。所以,若是個人一系列函數不會生成比鏈接持續時間更長的數據,那麼我就能夠徹底從鏈接池中分配內存,並知道在鏈接結束時,這些內存會被自動釋放。另外,有一些實現容許註冊 清除函數(cleanup functions),在清除內存池以前,剛好能夠調用它,來完成在內存被清理前須要完成的其餘全部任務(相似於面向對象中的析構函數)。
要在本身的程序中使用池,您既可使用 GNU libc 的 obstack 實現,也可使用 Apache 的 Apache Portable Runtime。GNU obstack 的好處在於,基於 GNU 的 Linux 發行版本中默認會包括它們。Apache Portable Runtime 的好處在於它有不少其餘工具,能夠處理編寫多平臺服務器軟件全部方面的事情。要深刻了解 GNU obstack 和 Apache 的池式內存實現,請參閱 參考資料部分中指向這些實現的文檔的連接。
下面的假想代碼列表展現瞭如何使用 obstack:
#include <obstack.h> #include <stdlib.h> /* Example code listing for using obstacks */ /* Used for obstack macros (xmalloc is a malloc function that exits if memory is exhausted */ #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free /* Pools */ /* Only permanent allocations should go in this pool */ struct obstack *global_pool; /* This pool is for per-connection data */ struct obstack *connection_pool; /* This pool is for per-request data */ struct obstack *request_pool; void allocation_failed() { exit(1); } int main() { /* Initialize Pools */ global_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(global_pool); connection_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(connection_pool); request_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(request_pool); /* Set the error handling function */ obstack_alloc_failed_handler = &allocation_failed; /* Server main loop */ while(1) { wait_for_connection(); /* We are in a connection */ while(more_requests_available()) { /* Handle request */ handle_request(); /* Free all of the memory allocated * in the request pool */ obstack_free(request_pool, NULL); } /* We're finished with the connection, time * to free that pool */ obstack_free(connection_pool, NULL); } } int handle_request() { /* Be sure that all object allocations are allocated * from the request pool */ int bytes_i_need = 400; void *data1 = obstack_alloc(request_pool, bytes_i_need); /* Do stuff to process the request */ /* return */ return 0; } |
基本上,在操做的每個主要階段結束以後,這個階段的 obstack 會被釋放。不過,要注意的是,若是一個過程須要分配持續時間比當前階段更長的內存,那麼它也可使用更長期限的 obstack,好比鏈接或者全局內存。傳遞給 obstack_free()
的 NULL
指出它應該釋放 obstack 的所有內容。能夠用其餘的值,可是它們一般不怎麼實用。
使用池式內存分配的益處以下所示:
池式內存的缺點是:
垃圾收集(Garbage collection)是全自動地檢測並移除再也不使用的數據對象。垃圾收集器一般會在當可用內存減小到少於一個具體的閾值時運行。一般,它們以程序所知的可用的一組「基本」數據 —— 棧數據、全局變量、寄存器 —— 做爲出發點。而後它們嘗試去追蹤經過這些數據鏈接到每一塊數據。收集器找到的都是有用的數據;它沒有找到的就是垃圾,能夠被銷燬並從新使用這些無用的數據。爲了有效地管理內存,不少類型的垃圾收集器都須要知道數據結構內部指針的規劃,因此,爲了正確運行垃圾收集器,它們必須是語言自己的一部分。
Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,由於它是免費的,並且既是保守的又是增量的,可使用 --enable-redirect-malloc
選項來構建它,而且能夠將它用做系統分配程序的簡易替代者(drop-in replacement)(用 malloc
/ free
代替它本身的 API)。實際上,若是這樣作,您就可使用與咱們在示例分配程序中所使用的相同的 LD_PRELOAD
技巧,在系統上的幾乎任何程序中啓用垃圾收集。若是您懷疑某個程序正在泄漏內存,那麼您可使用這個垃圾收集器來控制進程。在早期,當 Mozilla 嚴重地泄漏內存時,不少人在其中使用了這項技術。這種垃圾收集器既能夠在 Windows® 下運行,也能夠在 UNIX 下運行。
垃圾收集的一些優勢:
其缺點包括:
一切都須要折衷:性能、易用、易於實現、支持線程的能力等,這裏只列出了其中的一些。爲了知足項目的要求,有不少內存管理模式能夠供您使用。每種模式都有大量的實現,各有其優缺點。對不少項目來講,使用編程環境默認的技術就足夠了,不過,當您的項目有特殊的須要時,瞭解可用的選擇將會有幫助。下表對比了本文中涉及的內存管理策略。
策略 | 分配速度 | 回收速度 | 局部緩存 | 易用性 | 通用性 | 實時可用 | SMP 線程友好 |
定製分配程序 | 取決於實現 | 取決於實現 | 取決於實現 | 很難 | 無 | 取決於實現 | 取決於實現 |
簡單分配程序 | 內存使用少時較快 | 很快 | 差 | 容易 | 高 | 否 | 否 |
GNU malloc |
中 | 快 | 中 | 容易 | 高 | 否 | 中 |
Hoard | 中 | 中 | 中 | 容易 | 高 | 否 | 是 |
引用計數 | N/A | N/A | 很是好 | 中 | 中 | 是(取決於 malloc 實現) |
取決於實現 |
池 | 中 | 很是快 | 極好 | 中 | 中 | 是(取決於 malloc 實現) |
取決於實現 |
垃圾收集 | 中(進行收集時慢) | 中 | 差 | 中 | 中 | 否 | 幾乎不 |
增量垃圾收集 | 中 | 中 | 中 | 中 | 中 | 否 | 幾乎不 |
增量保守垃圾收集 | 中 | 中 | 中 | 容易 | 高 | 否 | 幾乎不 |
Web 上的文檔
基本的分配程序
malloc
實現。 mmap()
的 malloc
實現。 池式分配程序
智能指針和定製分配程序
垃圾收集器
關於現代操做系統中的虛擬內存的文章
關於 malloc 的文章
malloc
以及它如何與 BSD 虛擬內存交互。 關於定製分配程序的文章
關於垃圾收集的文章
Web 上的通用參考資料
書籍
來自 developerWorks