使用EMM還是使用AMM

動態內存管理DMM(Dynamic Memory Management)是從Heap中直接分配內存和回收內存。

有兩種方法實現動態內存管理。

一是顯示內存管理EMM(Explicit Memory Management)。
在EMM方式,內存從Heap中進行分配,用完後手動回收。程序使用malloc()函數分配整數數組,並使用free()函數釋放分配的內存。

二是自動內存管理AMM(Automatic Memory Management)。
AMM也可叫垃圾回收器(Garbage Collection)。Java編程語言實現了AMM,與EMM不同,Run-time system關注已分配的內存空間,一旦不再使用,立即回收。

無論是EMM還是AMM,所有的Heap管理計劃都面臨一些共同的問題和前在的缺陷:
1)內部碎片(Internal Fragmentation)
當內存有浪費時,內部碎片出現。因爲內存請求可導致分配的內存塊過大。比如請求128字節的存儲空間,結果Run-time system分配了512字節。

2)外部碎片(External Fragmentation)
當一系列的內存請求留下了數個有效的內存塊,但這些內存塊的大小均不能滿足新請求服務,此時出現外部碎片。

3)基於定位的延遲(Location-based Latency)
延遲問題出現在兩個數據值存儲得相隔很遠,導致訪問時間增加。

EMM往往比AMM更快。
EMM與AMM比較表:
——————————————————————————————————————
                                 EMM                                        AMM
——————————————————————————————————————
Benefits     尺寸更小、速度更快、易控制         stay focused on domain issues
Costs        複雜、記賬、內存泄露、指針懸空           不錯的性能
——————————————————————————————————————

早期的垃圾回收器非常慢,往往佔用50%的執行時間。

垃圾回收器理論產生於1959年,Dan Edwards在Lisp編程語言的開發時實現了第一個垃圾回收器。

垃圾回收器有三種基本的經典算法:

1)Reference counting(引用計數)
基本思想是:當對象創建並賦值時該對象的引用計數器置1,每當對象給任意變量賦值時,引用記數+1;一旦退出作用域則引用記數-1。一旦引用記數變爲0,則該對象可以被垃圾回收。
引用記數有其相應的優勢:對程序的執行來說,每次操作只需要花費很小塊的時間。這對於不能被過長中斷的實時系統來說有着天然的優勢。
但也有其不足:不能夠檢測到環(兩個對象的互相引用);同時在每次增加或者減少引用記數的時候比較費時間。
在現代的垃圾回收算法中,引用記數已經不再使用。

2)Mark-sweep(標記清理)
基本思想是:每次從根集出發尋找所有的引用(稱爲活對象),每找到一個,則對其做出標記,當追蹤完成之後,所有的未標記對象便是需要回收的垃圾。
也叫追蹤算法,基於標記並清除。這個垃圾回收步驟分爲兩個階段:在標記階段,垃圾回收器遍歷整棵引用樹並標記每一個遇到的對象。在清除階段,未標記的對象被釋放,並使其在內存中可用。

3)Copying collection(複製收集)
基本思想是:將內存劃分爲兩塊,一塊是當前正在使用;另一塊是當前未用。每次分配時使用當前正在使用內存,當無可用內存時,對該區域內存進行標記,並將標記的對象全部拷貝到當前未用內存區,這是反轉兩區域,即當前可用區域變爲當前未用,而當前未用變爲當前可用,繼續執行該算法。
拷貝算法需要停止所有的程序活動,然後開始冗長而繁忙的copy工作。這點是其不利的地方。

近年來還有兩種算法:

1)Generational garbage collection(分代)
其思想依據是:
  (1) 被大多數程序創建的大多數對象有着非常短的生存期。
  (2) 被大多數程序創建的部分對象有着非常長的生存期。
簡單拷貝算法的主要不足是它們花費了更多的時間去拷貝了一些長期生存的對象。
而分代算法的基本思想是:將內存區域分兩塊(或更多),其中一塊代表年輕代,另一塊代表老的一代。針對不同的特點,對年輕一代的垃圾收集更爲頻繁,對老代的收集則較少,每次經過年輕一代的垃圾回收總會有未被收集的活對象,這些活對象經過收集之後會增加成熟度,當成熟度到達一定程度,則將其放進老代內存塊中。
分代算法很好的實現了垃圾回收的動態性,同時避免了內存碎片,是目前許多JVM使用的垃圾回收算法。

2)Conservative garbage collection(保守)

哪一種算法最好?答案是沒有最好。

EMM作爲很常用的垃圾回收算法,有5種基本方法:
1)Table-driven algorithms
表驅動算法把內存分爲固定尺寸的塊集合。這些塊使用抽象數據結構進行索引。比如一個bit對應一個塊,用0和1表示是否分配。不利因素:位映射依賴於內存塊的尺寸;另外,搜索一系列的空閒內存塊可能需要搜索整個bit映射表,這影響性能。

2)Sequential fit
順序適應算法允許內存分爲不同的尺寸。此算法跟蹤已分配和空閒的Heap,標記空閒塊的起始地址和結束地址。它有三種子分類:
  (1) First fit(首次適應)——分配找到的第一個適合內存請求的塊
  (2) Best fit(最佳適應)——分配最適合內存請求的塊
  (3) Worst fit(最不適應)——分配最大的塊給內存請求

3)Buddy systems
Buddy systems算法的主要目的是加速已分配內存在釋放後的合併速度。顯示內存管理EMM使用Buddy systems算法可能導致內部碎片。

4)Segregated storage
隔離存儲技術涉及到把Heap分成多個區域(zone),併爲每個區域採用不同的內存管理計劃。這是很有效的方法。

5)Sub-allocators
子配置技術嘗試解決在Run-time System下分配大塊內存並單獨管理的內存分配問題。換句話說,程序完全負責自己的私有存儲堆(stockpile)的內存分配和回收,無需run-time System的幫助。它可能帶來額外的複雜性,但是你可以顯著地提高性能。在1990年的《C Compiler Design》一書中,Allen Holub就極好地利用了Sub-allocators來加速其編譯器的實現。

注意,顯示內存管理EMM必須是靈活的,能夠響應數種不同類型的請求。

最後,使用EMM還是使用AMM?這是一個Religious question,憑個人喜好。EMM在複雜的開銷下實現了速度和控制。AMM犧牲了性能,但換來了簡單性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章