lisp 标记形心_标记-压缩算法
前言
內(nèi)存碎片一直是非移動垃圾回收器(指在垃圾回收時不進行對象的移動)的一個問題,比如說在前面的標記-清除垃圾回收器就有這樣的問題。而標記-壓縮垃圾回收算法能夠有效的緩解這一問題。
算法原理
既然叫標記-壓縮算法,那么它也分為兩個階段,一個是標記(mark),一個是壓縮(compact). 其中標記階段跟標記-清除算法中的標記階段是一樣的,可以參考前面的文章。
而對于壓縮階段,它的工作就是移動所有的可達對象到堆內(nèi)存的同一個區(qū)域中,使他們緊湊的排列在一起,從而將所有非可達對象釋放出來的空閑內(nèi)存都集中在一起,通過這樣的方式來達到減少內(nèi)存碎片的目的。
在壓縮階段,由于要移動可達對象,那么需要考慮移動對象時的順序,一般分為下面三種:
任意順序 - 即不考慮原先對象的排列順序,也不考慮對象間的引用關(guān)系,隨意的移動可達對象,這樣可能會有內(nèi)存訪問的局部性問題。
線性順序 - 在重新排列對象時,會考慮對象間的引用關(guān)系,比如A對象引用了B對象,那么就會盡可能的將A,B對象排列在一起。
滑動順序 - 顧名思義,就是在重新排列對象時,將對象按照原先堆內(nèi)存中的排列順序滑動到堆的一端。
現(xiàn)在大多數(shù)的垃圾收集算法都是按照任意順序或滑動順序去實現(xiàn)的。下面我們分別來看下它們各自的算法原理。
Two-Finger 算法
Two-Finger算法來自Edwards, 它在壓縮階段移動對象時是任意順序移動的,它最適用于處理包含固定大小對象的內(nèi)存區(qū)域。由于Mark階段都是跟標記-清除算法一致的,這里我們只關(guān)注Compact階段。
Two-Finger算法是一個Two Passes算法,即需要遍歷堆內(nèi)存兩次,第一次遍歷是將堆末尾的可達對象移動到堆開始的空閑內(nèi)存單元去,第二次遍歷則需要修改可達對象的引用,因為一些可達對象已經(jīng)被移動到別的地址,而原先引用它們的對象還指向著它們移動前的地址。
在這兩次遍歷過程中,首尾兩個指針分別從堆的頭尾兩個位置向中間移動,直至兩個指針相遇,由于它們的運動軌跡酷似兩根手指向中間移動的軌跡,因此稱為Two Finger算法。
第一次遍歷
下面我們先看下第一遍遍歷的偽代碼 - 來自<>,
compact():
relocate(HeapStart,HeapEnd)
updateReferences(HeapStart,free)
relocate(start,end)
free
scan
while free < scan
//找到一個可以被釋放的空間
while isMarked(free)
unsetMarked(free)
free
//找到一個可以移動的可達對象
while not isMarked(scan) && scan > free
scan
if scan > free
unsetMarked(scan)
move(scan, free) //將scan位置的可達對象移動到free位置上
*scan
free
scan
第一次遍歷的原理是,頭指針(free)沿著堆頭向堆尾前進,直到找到一個空閑的內(nèi)存單元(即沒有被標記為可達對象的內(nèi)存單元),如遇到可達對象,則清除其標記。接著尾指針(scan)從堆尾向堆頭方向前進,直到找到一個被標記為可達的內(nèi)存單元。最后,collector將可達對象從尾指針(scan)指向的位置移動到頭指針(free)指向的位置,最后將可達對象移動后的位置(當前free指針指向的位置)寫到原先可達對象處于的位置(當前尾指針scan指向的位置), 為下一次的遍歷 - 更新對象相互間的引用做好準備。注:當移動可達對象時,其引用的對象在可達對象移動后保持不變,如下圖中的G對象移動后依然指向位置5和位置10。
第一次遍歷
第二次遍歷
而第二次的遍歷則是為了更新引用關(guān)系,一個可達對象可以被其他對象引用,比如上圖中的K對象,如果其被移動后,引用它的對象比如說G并不知道它被移動了,那么這第二次的遍歷就是為了告訴G它所引用的對象K已經(jīng)被移動到新的位置上去了,它需要更新它對K的引用。
updateReferences(start,end)
for each fld in Roots //先更新mutator根對象所引用的對象關(guān)系
ref
if ref >= end
*fld
scan
while scan < ned
for each fld in Pointers(scan)
ref
if ref >= end
*fld
scan
第二次遍歷,collector先會對根對象進行遍歷,比如根對象2引用著位置6的內(nèi)存單元,根據(jù)算法,該位置大于等于end指針所指向的位置 - 即第一次遍歷free指針和scan指針相遇的位置,那么我們就認為這個位置的對象已經(jīng)被移動,需要更新根對象2的引用關(guān)系,即從引用位置6改為引用位置2(位置6的內(nèi)存單元中記錄著該對象被移動后的新位置)。同理,在移動G對象的時候,也是要判斷看G所引用的內(nèi)存單元位置是否大于end指針指向的位置,如果小于,則不處理。否則則修改G的引用關(guān)系。
第二次遍歷
LISP2 算法
Lisp2算法是一種應(yīng)用更為廣泛的壓縮算法,它屬于滑動順序算法中的一種。它跟Two-Finger算法的不同還在于它可以處理不同大小的對象,而不再是固定大小的對象。同時,計算出來的可達對象的遷移地址需要額外的空間進行存儲而不再是復(fù)寫原先對象所在的位置。最后,Lips2算法需要進行3次堆內(nèi)存的遍歷。
第一次遍歷
第一次遍歷,collecor僅僅是計算和記錄可達對象應(yīng)該遷移去的地址。
compact():
computeLocations(HeapStart,HeapEnd,HeapStart)
updateReferences(HeapStart,HeapEnd)
relocate(HeapStart,HeapEnd)
computeLocations(start,end,toRegion):
scan
free
while scan < end
if isMarked(scan)
forwardingAddress(scan)
free
scan
第一次遍歷
指針free, scan同時指向堆起始位置,同時scan指針向堆尾移動,目的是要找到被標記的可達對象。
找到可達對象后,在scan指針對應(yīng)的位置分配一個額外的空間來存儲該可達對象應(yīng)該遷移到的地址 - 就是free指針指向的位置0,同時free指針向堆尾移動B對象大小的距離- free'指針指向的位置。最后scan指針繼續(xù)往前走,直到尋找到下一個可達對象D - scan'指針指向的位置。
同理,在可達對象D處分配一塊空間來保存對象D應(yīng)該遷移到的位置,由于B對象已經(jīng)占用了2個內(nèi)存單元,所以對象E的遷移地址是從位置2開始,也就是當前free指針指向的位置。
指針free,scan繼續(xù)向前移動。
第一次遍歷完后,所有的可達對象都有了對應(yīng)的遷移地址,free指針指向位置9,因為所有的可達對象總共占了9個單元大小的空間。
第二次遍歷
第二次遍歷主要是修改對象間的引用關(guān)系,基本跟Two Finger算法的第二次遍歷一樣。
updateReferences(start,end):
for each fld in Roots
ref
if ref != null
*fld
scan
while scan < end
if isMarked(scan)
for each fld in Pointers(scan)
if *fld != null
*fld
scan
第二次遍歷
修改根對象的引用關(guān)系,根對象1引用對象B,對象B的遷移地址為0,于是collector將根對象對B對象的引用指向它的遷移地址 - 位置0, 現(xiàn)在A對象所處的位置。
同理,對于根對象2,3都執(zhí)行同樣的操作,將它們對其所引用的對象的引用修改為對應(yīng)的它們所引用的對象的遷移地址。
通過scan指針遍歷堆內(nèi)存,更新所有的可達對象對其引用對象的引用為其引用對象的遷移地址。比如說,對于可達對象B, 它引用了對象D,D的遷移地址是2,那么B直接將其對D對象的引用重新指向2這個位置。
第二次遍歷結(jié)束后的對象之間的引用關(guān)系。
第三次遍歷
第三次遍歷則是根據(jù)可達對象的遷移地址去移動可達對象,比如說可達對象B,它的遷移地址是0,那么就將其移動到位置0,同時去除可達對象的標記,以便下次垃圾收集。
relocate(start,end):
scan
while scan < end
if isMarked(scan)
dest
move(scan,dest) //將可達對象從scan位置移動到dest位置
unsetMarked(dest)
scan
所有可達對象移動結(jié)束后,內(nèi)存單元展示為:
第三次遍歷
后記
通過上面的兩種算法的展示,我想大家應(yīng)該都大概了解了標記-壓縮算法工作的基本原理,除了上面兩種實現(xiàn),還有其它的一些壓縮算法的實現(xiàn),這里就不一一陳列。有興趣者可以自己到網(wǎng)絡(luò)上搜索。
標記-壓縮算法雖然緩解的內(nèi)存碎片問題,但是它也引用了額外的開銷,比如說額外的空間來保存遷移地址,需要遍歷多次堆內(nèi)存等。
總結(jié)
以上是生活随笔為你收集整理的lisp 标记形心_标记-压缩算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js 转换数字为decmail_BigD
- 下一篇: mysql 备份 第三方工具_Mysql