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

前言

垃圾自動回收機制的出現使編程更加的簡單,使得咱們不須要再去考慮內存分配和釋放的問題,而是更加的專一在咱們產品功能的實現上。可是咱們仍是須要花時間去了解下垃圾收集機制是怎麼工做的,以便後面可以更好的進行咱們應用的性能調優等。java

目前最基本的垃圾收集算法有四種,標記-清除算法(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中的變量和分配在棧上的變量 - 方法內部的臨時變量等都屬於此類). atom

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

算法原理

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

在標記階段,collector從mutator根對象開始進行遍歷,對從mutator根對象能夠訪問到的對象都打上一個標識,通常是在對象的header中,將其記錄爲可達對象。code

而在清除階段,collector對堆內存(heap memory)從頭至尾進行線性的遍歷,若是發現某個對象沒有標記爲可達對象-經過讀取對象的header信息,則就將其回收。orm

從上圖咱們能夠看到,在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 ref

atomic 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。

相關文章
相關標籤/搜索