1. 程式人生 > >JAVA 垃圾回收

JAVA 垃圾回收

計數器 多余 hub -s 沒有 回收 技術 區域 進行

垃圾回收

垃圾回收主要思考三件事情:

  • 哪種內存需要回收?
  • 什麽時候回收?
  • 怎麽回收?

第一步、判斷對象是否存活

引用計數法

這是一種非常簡單易理解的回收算法。每當有一個地方引用一個對象的時候則在引用計數器上 +1,當失效的時候就 -1,無論什麽時候計數器為 0 的時候則認為該對象死亡可以回收了。

這種算法雖然簡單高效,但是卻無法解決循環引用的問題,因此 Java 虛擬機並沒有采用這種算法。

  循環引用

  對象A引用了B,而對象B也引用了A。用代碼表示形如下

  Dog dog = new dog();

  Brand brand = new Bran();

  dog.brand = brand;

  brand.dog = dog;

  

可達性分析算法

主流的語言其實都是采用可達性分析算法:

可達性算法是通過一個稱為 GC Roots 的對象向下搜索,整個搜索路徑就稱為引用鏈,當一個對象到 GC Roots 沒有任何引用鏈 JVM 就認為該對象是可以被回收的。

技術分享圖片

如圖:Object1、2、3、4 都是存活的對象,而 Object5、6、7都是可回收對象。

可以用作 GC-Roots 的對象有:

  • 方法區中靜態變量所引用的對象。
  • 虛擬機棧中所引用的對象。

第二步,垃圾回收

垃圾回收算法

標記-清除算法

標記清除算法分為兩個步驟,標記和清除。 首先將需要回收的對象標記起來,然後統一清除。但是存在兩個主要的問題:

  • 標記和清除的效率都不高。
  • 清除之後容易出現不連續內存,當需要分配一個較大內存時就不得不需要進行一次垃圾回收。

標記清除過程如下:

技術分享圖片

復制算法

復制算法是將內存劃分為兩塊大小相等的區域,每次使用時都只用其中一塊區域,當發生垃圾回收時會將存活的對象全部復制到未使用的區域,然後對之前的區域進行全部回收。

這樣簡單高效,而且還不存在標記清除算法中的內存碎片問題,但就是有點浪費內存。

在新生代會使用該算法。

新生代中分為一個 Eden 區和兩個 Survivor 區。通常兩個區域的比例是 8:1:1 ,使用時會用到 Eden 區和其中一個 Survivor

區。當發生回收時則會將還存活的對象從 EdenSurvivor 區拷貝到另一個 Survivor 區,當該區域內存也不足時則會使用分配擔保利用老年代來存放內存。

復制算法過程:

技術分享圖片

標記整理算法

復制算法如果在存活對象較多時效率明顯會降低,特別是在老年代中並沒有多余的內存區域可以提供內存擔保。

所以老年代中使用的時候分配整理算法,它的原理和分配清除算法類似,只是最後一步的清除改為了將存活對象全部移動到一端,然後再將邊界之外的內存全部回收。

技術分享圖片

分代回收算法

現代多數的商用 JVM 的垃圾收集器都是采用的分代回收算法,和之前所提到的算法並沒有新的內容。

只是將 Java 堆分為了新生代和老年代。由於新生代中存活對象較少,所以采用復制算法,簡單高效。

而老年代中對象較多,並且沒有可以擔保的內存區域,所以一般采用標記清除或者是標記整理算法

參考

  • https://github.com/crossoverJie/JCSprout/blob/master/MD/GarbageCollection.md

JAVA 垃圾回收