JVM--標記-清除演算法Mark-Sweep

來源:互聯網
上載者:User

標籤:

前言

垃圾自動回收機制的出現使編程更加的簡單,使得我們不需要再去考慮記憶體配置和釋放的問題,而是更加的專註在我們產品功能的實現上。但是我們還是需要花時間去瞭解下垃圾收集機制是怎麼工作的,以便後面能夠更好的進行我們應用的效能調優等。

目前最基本的垃圾收集演算法有四種,標記-清除演算法(mark-sweep),標記-壓縮演算法(mark-compact),複製演算法(copying)以及引用計數演算法(reference counting).而現代流行的垃圾收集演算法一般是由這四種中的其中幾種演算法相互組合而成,比如說,對堆(heap)的一部分採用標記-清除演算法,對堆(heap)的另外一部分則採用複製演算法等等。今天我們主要來看下標記-清除演算法的原理。

基本概念

在瞭解標記-清除演算法前,我們先要瞭解幾個基本概念。

首先是mutator和collector,這兩個名詞經常在垃圾收集演算法中出現,collector指的就是垃圾收集器,而mutator是指除了垃圾收集器之外的部分,比如說我們應用程式本身。mutator的職責一般是NEW(分配記憶體),READ(從記憶體中讀取內容),WRITE(將內容寫入記憶體),而collector則就是回收不再使用的記憶體來供mutator進行NEW操作的使用。

第二個基本概念是關於mutator roots(mutator根對象),mutator根對象一般指的是分配在堆記憶體之外,可以直接被mutator直接存取到的對象,一般是指靜態/全域變數以及Thread-Local變數(在Java中,儲存在java.lang.ThreadLocal中的變數和分配在棧上的變數 - 方法內部的臨時變數等都屬於此類). 

第三個基本概念是關於可達對象的定義,從mutator根對象開始進行遍曆,可以被訪問到的對象都稱為是可達對象。這些對象也是mutator(你的應用程式)正在使用的對象。

演算法原理

顧名思義,標記-清除演算法分為兩個階段,標記(mark)和清除(sweep). 

在標記階段,collector從mutator根對象開始進行遍曆,對從mutator根對象可以訪問到的對象都打上一個標識,一般是在對象的header中,將其記錄為可達對象。

而在清除階段,collector對堆記憶體(heap memory)從頭到尾進行線性遍曆,如果發現某個對象沒有標記為可達對象-通過讀取對象的header資訊,則就將其回收。

從我們可以看到,在Mark階段,從根對象1可以訪問到B對象,從B對象又可以訪問到E對象,所以B,E對象都是可達的。同理,F,G,J,K也都是可達對象。到了Sweep階段,所有非可達對象都會被collector回收。同時,Collector在進行標記和清除階段時會將整個應用程式暫停(mutator),等待標記清除結束後才會恢複應用程式的運行,這也是Stop-The-World這個單詞的來曆。

接著我們先看下一般垃圾收集動作是怎麼被觸發的,下面是mutator進行NEW操作的虛擬碼:

New():    ref <- allocate()  //分配新的記憶體到ref指標    if ref == null       collect()  //記憶體不足,則觸發垃圾收集       ref <- allocate()       if ref == null          throw "Out of Memory"   //垃圾收集後仍然記憶體不足,則拋出Out of Memory錯誤          return refatomic collect():    markFromRoots()    sweep(HeapStart,HeapEnd)

而下面是對應的mark演算法:

markFromRoots():    worklist <- empty    for each fld in Roots  //遍曆所有mutator根對象        ref <- *fld        if ref != null && isNotMarked(ref)  //如果它是可達的而且沒有被標記的,直接標記該對象並將其加到worklist中           setMarked(ref)           add(worklist,ref)           mark()mark():    while not isEmpty(worklist)          ref <- remove(worklist)  //將worklist的最後一個元素彈出,賦值給ref          for each fld in Pointers(ref)  //遍曆ref對象的所有指標域,如果其指標域(child)是可達的,直接標記其為可達對象並且將其加入worklist中          //通過這樣的方式來實現深度遍曆,直到將該對象下面所有可以訪問到的對象都標記為可達對象。                child <- *fld                if child != null && isNotMarked(child)                   setMarked(child)                   add(worklist,child)

在mark階段結束後,sweep演算法就比較簡單了,它就是從堆記憶體起始位置開始,線性遍曆所有對象直到堆記憶體末尾,如果該對象是可達對象的(在mark階段被標記過的),那就直接去除標記位(為下一次的mark做準備),如果該對象是不可達的,直接釋放記憶體。

sweep(start,end):    scan <- start   while scan < end       if isMarked(scan)          setUnMarked(scan)      else          free(scan)      scan <- nextObject(scan)

缺點

標記-清除演算法的比較大的缺點就是垃圾收集後有可能會造成大量的記憶體片段,像上面的圖片所示,垃圾收集後記憶體中存在三個記憶體片段,假設一個方格代表1個單位的記憶體,如果有一個對象需要佔用3個記憶體單位的話,那麼就會導致Mutator一直處於暫停狀態,而Collector一直在嘗試進行垃圾收集,直到Out of Memory。

JVM--標記-清除演算法Mark-Sweep

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.