JAVA 垃圾回收
垃圾回收
垃圾回收主要思考三件事情:
- 哪種內存需要回收?
- 什麽時候回收?
- 怎麽回收?
第一步、判斷對象是否存活
引用計數法
這是一種非常簡單易理解的回收算法。每當有一個地方引用一個對象的時候則在引用計數器上 +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
Eden
,Survivor
區拷貝到另一個 Survivor
區,當該區域內存也不足時則會使用分配擔保利用老年代來存放內存。
復制算法過程:
標記整理算法
復制算法如果在存活對象較多時效率明顯會降低,特別是在老年代中並沒有多余的內存區域可以提供內存擔保。
所以老年代中使用的時候分配整理算法
,它的原理和分配清除算法
類似,只是最後一步的清除改為了將存活對象全部移動到一端,然後再將邊界之外的內存全部回收。
分代回收算法
現代多數的商用 JVM
的垃圾收集器都是采用的分代回收算法,和之前所提到的算法並沒有新的內容。
只是將 Java 堆分為了新生代和老年代。由於新生代中存活對象較少,所以采用復制算法,簡單高效。
而老年代中對象較多,並且沒有可以擔保的內存區域,所以一般采用標記清除或者是標記整理算法。
參考
- https://github.com/crossoverJie/JCSprout/blob/master/MD/GarbageCollection.md
JAVA 垃圾回收