在堆里存放著幾乎多有的java對象實例堰酿,垃圾搜集器在對堆進(jìn)行回收之前,第一件事情就是確定這些對象之中哪些還“存活”著(即通過任何途徑都無法使用的對象)张足。
一触创、可達(dá)性分析算法
在Java中,是通過可達(dá)性分析(Reachability Analysis)來判定對象是否存活的为牍。該算法的基本思路就是通過一些被稱為引用鏈(GC Roots)的對象作為起點哼绑,從這些節(jié)點開始向下搜索,搜索走過的路徑被稱為(Reference Chain)吵聪,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時(即從GC Roots節(jié)點到該節(jié)點不可達(dá))凌那,則證明該對象是不可用的。
如上圖所示吟逝,object1~object4對GC Root都是可達(dá)的帽蝶,說明不可被回收,object5和object6對GC Root節(jié)點不可達(dá)块攒,說明其可以被回收励稳。
在Java中,可作為GC Root的對象包括以下幾種:
- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI(即一般說的Native方法)引用的對象
二囱井、finalize()方法最終判定對象是否存活
即使在可達(dá)性分析算法中不可達(dá)的對象驹尼,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段庞呕,要真正宣告一個對象死亡新翎,至少要經(jīng)歷再次標(biāo)記過程。
標(biāo)記的前提是對象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈住练。
1. 第一次標(biāo)記并進(jìn)行一次篩選地啰。
篩選的條件是此對象是否有必要執(zhí)行finalize()方法。
當(dāng)對象沒有覆蓋finalize方法讲逛,或者finzlize方法已經(jīng)被虛擬機(jī)調(diào)用過亏吝,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”,對象被回收盏混。
2. 第二次標(biāo)記
如果這個對象被判定為有必要執(zhí)行finalize()方法蔚鸥,那么這個對象將會被放置在一個名為:F-Queue的隊列之中,并在稍后由一條虛擬機(jī)自動建立的许赃、低優(yōu)先級的Finalizer線程去執(zhí)行止喷。這里所謂的“執(zhí)行”是指虛擬機(jī)會觸發(fā)這個方法,但并不承諾會等待它運行結(jié)束混聊。這樣做的原因是启盛,如果一個對象finalize()方法中執(zhí)行緩慢,或者發(fā)生死循環(huán)(更極端的情況),將很可能會導(dǎo)致F-Queue隊列中的其他對象永久處于等待狀態(tài)僵闯,甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)崩潰卧抗。
Finalize()方法是對象脫逃死亡命運的最后一次機(jī)會,稍后GC將對F-Queue中的對象進(jìn)行第二次小規(guī)模標(biāo)記鳖粟,如果對象要在finalize()中成功拯救自己----只要重新與引用鏈上的任何的一個對象建立關(guān)聯(lián)即可社裆,譬如把自己賦值給某個類變量或?qū)ο蟮某蓡T變量,那在第二次標(biāo)記時它將移除出“即將回收”的集合向图。如果對象這時候還沒逃脫泳秀,那基本上它就真的被回收了。
三榄攀、Java引用
從可達(dá)性算法中可以看出嗜傅,判斷對象是否可達(dá)時,與“引用”有關(guān)檩赢。那么什么情況下可以說一個對象被引用吕嘀,引用到底代表什么?
在JDK1.2之后贞瞒,Java對引用的概念進(jìn)行了擴(kuò)充偶房,可以將引用分為以下四類:
- 強(qiáng)引用(Strong Reference)
- 軟引用(Soft Reference)
- 弱引用(Weak Reference)
- 虛引用(Phantom Reference)
這四種引用從上到下,依次減弱
3.1 強(qiáng)引用
強(qiáng)引用就是指在程序代碼中普遍存在的军浆,類似Object obj = new Object()
這類似的引用棕洋,只要強(qiáng)引用在,垃圾搜集器永遠(yuǎn)不會搜集被引用的對象乒融。也就是說掰盘,寧愿出現(xiàn)內(nèi)存溢出,也不會回收這些對象赞季。
3.2 軟引用
軟引用是用來描述一些有用但并不是必需的對象愧捕,在Java中用java.lang.ref.SoftReference
類來表示。對于軟引用關(guān)聯(lián)著的對象碟摆,只有在內(nèi)存不足的時候JVM才會回收該對象。因此叨橱,這一點可以很好地用來解決OOM的問題典蜕,并且這個特性很適合用來實現(xiàn)緩存:比如網(wǎng)頁緩存、圖片緩存等罗洗。
import java.lang.ref.SoftReference;
public class Main {
public static void main(String[] args) {
SoftReference<String> sr = new SoftReference<String>(new String("hello"));
System.out.println(sr.get());
}
}
3.3 弱引用
弱引用也是用來描述非必需對象的愉舔,當(dāng)JVM進(jìn)行垃圾回收時,無論內(nèi)存是否充足伙菜,都會回收被弱引用關(guān)聯(lián)的對象轩缤。在java中,用java.lang.ref.WeakReference類來表示。下面是使用示例:
import java.lang.ref.WeakReference;
public class Main {
public static void main(String[] args) {
WeakReference<String> sr = new WeakReference<String>(new String("hello"));
System.out.println(sr.get());
System.gc(); //通知JVM的gc進(jìn)行垃圾回收
System.out.println(sr.get());
}
}
3.4 虛引用
虛引用和前面的軟引用火的、弱引用不同壶愤,它并不影響對象的生命周期。在java中用java.lang.ref.PhantomReference
類表示馏鹤。如果一個對象與虛引用關(guān)聯(lián)征椒,則跟沒有引用與之關(guān)聯(lián)一樣,在任何時候都可能被垃圾回收器回收湃累。
要注意的是勃救,虛引用必須和引用隊列關(guān)聯(lián)使用,當(dāng)垃圾回收器準(zhǔn)備回收一個對象時治力,如果發(fā)現(xiàn)它還有虛引用蒙秒,就會把這個虛引用加入到與之 關(guān)聯(lián)的引用隊列中。程序可以通過判斷引用隊列中是否已經(jīng)加入了虛引用宵统,來了解被引用的對象是否將要被垃圾回收晕讲。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動榜田。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class Main {
public static void main(String[] args) {
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
System.out.println(pr.get());
}
}
3.5 軟引用和弱引用進(jìn)一步說明
在SoftReference類中益兄,有三個方法,兩個構(gòu)造方法和一個get方法(WekReference類似):
public class SoftReference<T> extends Reference<T> {
/**
* Timestamp clock, updated by the garbage collector
*/
static private long clock;
/**
* Timestamp updated by each invocation of the get method. The VM may use
* this field when selecting soft references to be cleared, but it is not
* required to do so.
*/
private long timestamp;
/**
* Creates a new soft reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new soft reference will refer to
*/
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
/**
* Creates a new soft reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new soft reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*
*/
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns <code>null</code>.
*
* @return The object to which this reference refers, or
* <code>null</code> if this reference object has been cleared
*/
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
get方法用來獲取與軟引用關(guān)聯(lián)的對象的引用箭券,如果該對象被回收了净捅,則返回null。
在使用軟引用和弱引用的時候辩块,我們可以顯示地通過System.gc()來通知JVM進(jìn)行垃圾回收蛔六,但是要注意的是,雖然發(fā)出了通知废亭,JVM不一定會立刻執(zhí)行国章,也就是說這句是無法確保此時JVM一定會進(jìn)行垃圾回收的。
3.6 虛引用進(jìn)一步說明:
虛引用中有一個構(gòu)造函數(shù)豆村,可以看出液兽,其必須和一個引用隊列一起存在。get()方法永遠(yuǎn)返回null掌动,因為虛引用永遠(yuǎn)不可達(dá)四啰。
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
參考
- 《深入理解Java虛擬機(jī)》
- https://www.cnblogs.com/dolphin0520/p/3784171.html