JVM垃圾回收-垃圾收集算法總結

JVM虛擬機在判斷哪些對象須要回收以後,接下來就是如何回收這些對象的內存。能夠把系統內存想象成一個個小方格,jvm使用到的內存和須要收集到內存都散亂分佈。如何將須要回收的內存回收,並儘量提升效率和剩餘內存的規整?
算法

標記-清除算法

這個是最基礎的算法,顧名思義,這個算法分爲兩步:安全

  1. 首先標記出全部須要回收的對象
  2. 在標記完成後統一回收全部被標記的對象。

    標記-清除算法的缺點:
  • 效率問題,標記和清除兩個過程的效率都不高
  • 空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。

之因此說它是最基礎的收集算法,是由於後續的收集算法都是基於這種思路並對其不足進行改進而獲得的。數據結構

複製算法

爲了解決效率問題,一種稱爲「複製」(Copying)的收集算法出現了
實現步驟:jvm

  1. 將可用內存按容量劃分爲大小相等的兩塊
  2. 當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面
  3. 而後再把已使用過的內存空間一次清理掉
    缺點:將可用內存縮小了一半

在實際應用中,大部分新生代都使用這個算法,實際並不須要縮小一半這麼多。IBM公司的專門研究代表,新生代中的對象98%是「朝生夕死」的,因此並不須要按照1∶1的比例來劃份內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間。ide

標記-整理算法

與新生代不一樣,老年代的垃圾回收,通常大部分對象都會存活,若是使用標記-複製操做,不只效率會下降,而且也必須按照1:1的比例來分配內存空間,形成50%的空間浪費。根據老年代的特色,有人提出了另一種「標記-整理」(Mark-Compact)算法,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存
線程

分代收集算法

這種算法並無什麼新的思想,只是根據對象存活週期的不一樣將內存劃分爲幾塊。
通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。對象

  • 在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集
  • 而老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記—清理」或者「標記—整理」算法來進行回收
HotSpot虛擬機中的算法實現

枚舉根節點

垃圾收集的第一步,是肯定哪些對象已死,這裏使用的是可達性分析算法。第一步:枚舉根節點,即肯定可達性分析的GC ROOTS節點。如何根節點可能出現的地方很大,所有都須要掃描一遍,所以可達性分析對執行時間的敏感還體如今GC停頓上,由於這項分析工做必須在一個能確保一致性的快照中進行,——這裏「一致性」的意思是指在整個分析期間整個執行系統看起來就像被凍結在某個時間點上,不能夠出現分析過程當中對象引用關係還在不斷變化的狀況,該點不知足的話分析結果準確性就沒法獲得保證。blog

這點是致使GC進行時必須停頓全部Java執行線程(Sun將這件事情稱爲「Stop TheWorld」)的其中一個重要緣由,即便是在號稱(幾乎)不會發生停頓的CMS收集器中,枚舉根節點時也是必需要停頓的。進程

HoptSpot虛擬機中採用的是準確式GC,因此當執行系統停頓下來後,並不須要一個不漏地檢查完全部執行上下文和全局的引用位置。HoptSpot虛擬機中使用一組稱爲OopMap的數據結構來達到這個目的的,在類加載完成的時候,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程當中,也會在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣,在系統停頓時,就不須要在一一遍歷全部對象,只須要從OopMap直接獲取根節點就能夠了。內存

安全點

在OopMap的協助下,HotSpot能夠快速且準確地完成GC Roots枚舉,但一個很現實的問題隨之而來:可能致使引用關係變化,或者說OopMap內容變化的指令很是多,若是爲每一條指令都生成對應的OopMap,那將會須要大量的額外空間,這樣GC的空間成本將會變得很高。
因此,爲了解決這個問題,引入安全點的概念,即:不是全部操做都記錄OopMap,只在一些關鍵點記錄。這就意味着,當jvm想要中止運行時,不是馬上中止運行,而是等全部線程所有跑到安全點纔會真的中止。
這就引入了下一個問題:如何讓全部線程都跑到安全點?如今主要有兩種方案:搶先式中斷(Preemptive Suspension)和主動式中斷(VoluntarySuspension)

  • 搶先式中斷 :須要線程的執行代碼主動去配合,在GC發生時,首先把全部線程所有中斷,若是發現有線程中斷的地方不在安全點上,就恢復線程,讓它「跑」到安全點上。(幾乎沒有虛擬機用這個方法)
  • 主動式中斷:當GC須要中斷線程的時候,不直接對線程操做,僅僅簡單地設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌爲真時就本身中斷掛起

安全區域

安全點彷佛完美解決了進程如何進入GC的問題,可是有個前提,全部的線程都要「跑」到安全點才能夠,那麼若是線程處於Sleep狀態或者Blocked狀態,這時候線程沒法響應JVM的中斷請求,「走」到安全的地方去中斷掛起,JVM也顯然不太可能等待線程從新被分配CPU時間。對於這種狀況,就須要安全區域(Safe Region)來解決。
安全區域是指在一段代碼片斷之中,引用關係不會發生變化。在這個區域中的任意地方開始GC都是安全的。咱們也能夠把Safe Region看作是被擴展了的Safepoint。

簡單來講,就是一段代碼唄標識爲安全區域,線程執行到這裏時,會標記線程處於安全區域。那麼程序GC時,就無論這個線程,同時這個線程離開安全區域時,若是系統正在GC,也要等待GC完成以後再離開。 簡要地介紹了HotSpot虛擬機如何去發起內存回收的問題,可是虛擬機如何具體地進行內存回收動做仍然未涉及,由於內存回收如何進行是由虛擬機所採用的GC收集器決定的,而一般虛擬機中每每不止有一種GC收集器,這個問題再下一篇博客中探討

相關文章
相關標籤/搜索