JVM (四)--垃圾回收(一)
程式計數器、虛擬機器棧、本地方法棧這三個區域屬於執行緒私有,只存在於執行緒的生命週期內,執行緒結束之後也會消失,因此,不需要對這三個區域進行垃圾回收。垃圾回收主要針對方法區和Java堆進行。
一、判斷一個物件是否存活
1、引用計數演算法
給物件新增一個引用計數器,當物件增加一個引用時計數器加1,引用失效時計數器減1。引用計數器不為0的物件仍然存活。
兩個物件出項迴圈引用的時候,此時引用計數器永遠不為0,導致無法對它們進行垃圾回收:
public class ReferenceCountingGC { public Object instance = null; public static void main(String[] args) { ReferenceCountingGC objectA = new ReferenceCountingGC(); ReferenceCountingGC objectB = new ReferenceCountingGC(); objectA.instance = objectB; objectB.instance = objectA; } }
正因為迴圈引用的存在,因此Java虛擬機器不使用引用計數演算法。
2、可達性分析演算法:
通過GC Roots作為起始點進行搜尋,能夠到達的物件都是存活的,不可達的物件就是失效的,可以被回收。
Java虛擬機器使用該演算法來判斷物件是否可以被回收,在Java中GC Roots 一般包括以下內容:
- 虛擬機器棧中引用的物件
- 本地方法棧中引用的物件
- 方法區中類靜態屬性引用的物件
- 方法區中常量引用的物件
二、引用型別
無論是通過引用計數演算法判斷物件的引用數量,還是通過可達性分析演算法判斷物件是否可達,
判定物件是否可被回收都於引用有關。
Java中具有四種強度不同的引用型別:
(一)強引用
被強引用關聯的物件不會被垃圾收集器回收。
使用 new 一個新物件的方式來建立強引用。
Object obj = new Object();
(二)軟引用
被軟引用關聯的物件,只有在記憶體不夠的情況下才會被回收。
使用SoftReference類來建立軟引用。
// 獲取物件並快取 Object object = new Object(); SoftReference softRef = new SoftReference(object); // 從軟引用中獲取物件 Object object = (Object) softRef.get(); if (object == null){ // 當軟引用被回收後重新獲取物件 object = new Object(); }
(三)弱引用
被弱引用關聯的物件一定會被垃圾收集器回收,也就是說它只能存活到下一次垃圾收集發生之前。
使用WeakReference類來實現弱引用:
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
(四)虛引用
又稱為幽靈引用或者幻影引用。一個物件是否又虛引用的存在,完全不會對其生存時間構成影響,
也無法通過虛引用取得一個物件例項。
為一個物件設定虛引用關聯的唯一目的就是能在這個物件被收集器回收時收到一個系統通知。
使用PhantomReference來實現虛引用:
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj = null;
三、方法區的回收
因為方法區主要存在主要存放永久代物件,而永久代物件的回收率比新生代差很多,因此在方法區上進行回收價效比不高。
方法區的回收主要是常量池的回收和對類的解除安裝。
類的解除安裝條件很多,需要滿足一下3個條件,並且滿足了也不一定會被解除安裝:
- 該類的所用例項都已經被回收,也就是Java堆中不存在該類的任何例項。
- 載入該類的ClassLoader已經被回收。
- 該類對應java.lang.Class物件沒有在任何地方被引用,也就無法在任何地方通過反射訪問該類方法。
可以通過-Xnoclassgc 引數來控制是否對類進行解除安裝。
在大量使用反射、動態代理、cglib等ByteCode框架、動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機器舉辦解除安裝功能,以保證不會出現記憶體洩漏。
四、finalize()
finalize()類似於C++的析構,用來做關閉外部資源等工作。但try-finally等方法可以坐的更好,並且該方法執行代價高昂,不確定性大,無法保證各個物件的呼叫順序,因此最好不用使用。
當一個物件可以被回收時,如果需要執行該物件的finalize()方法,那麼就有可能通過在方法中讓物件重新被引用,從而實現自救。自救只能進行一次,如果回收物件之前呼叫了finalize()方法自救,後面回收時不會呼叫finalize()方法。