在Java語言中,除了基本數據類型外,其他的都是指向各類對象的對象引用;Java中根據其生命週期的長短,將引用分爲4類。
強引用、軟引用、弱引用、虛引用
各引用的講解
強引用
在平時編碼中,Object obj = new Object()中的obj就是強引用。通過關鍵字new創建的對象所關聯的引用就是強引用。 當JVM內存空間不足,JVM寧願拋出OutOfMemoryError運行時錯誤(OOM),使程序異常終止,也不會靠隨意回收具有強引用的“存活”對象來解決內存不足的問題。對於一個普通的對象,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值爲 null,就是可以被垃圾收集的了,具體回收時機還是要看垃圾收集策略。
軟引用
軟引用通過SoftReference類實現。 軟引用的生命週期比強引用短一些。只有當 JVM 認爲內存不足時,纔會去試圖回收軟引用指向的對象:即JVM 會確保在拋出 OutOfMemoryError 之前,清理軟引用指向的對象。軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。後續,我們可以調用ReferenceQueue的poll()方法來檢查是否有它所關心的對象被回收。如果隊列爲空,將返回一個null,否則該方法返回隊列中前面的一個Reference對象。
應用場景:軟引用通常用來實現內存敏感的緩存。如果還有空閒內存,就可以暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。
弱應用
弱引用通過WeakReference類實現。 弱引用的生命週期比軟引用短。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。由於垃圾回收器是一個優先級很低的線程,因此不一定會很快回收弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
應用場景:弱應用同樣可用於內存敏感的緩存。
虛引用
虛引用也稱爲幽靈引用或者幻象引用,通過PhantomReference類來實現。無法通過虛引用訪問對象的任何屬性或函數。幻象引用僅僅是提供了一種確保對象被 finalize 以後,做某些事情的機制。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取一些程序行動。
應用場景:可用來跟蹤對象被垃圾回收器回收的活動,當一個虛引用關聯的對象被垃圾收集器回收之前會收到一條系統通知。
代碼實現
public class Person {
private Integer id;
public Person(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
public class MainTest {
/**
* 軟引用
*/
@Test
public void test1() {
//在堆中創建一個對象Obj
//在棧中創建一個p來強引用此對象Obj
Person p = new Person(1);
//在棧中創建一個softReference來軟引用此對象Obj 可以獲取對象的屬性值
SoftReference<Person> softReference = new SoftReference<Person>(p);
System.out.println(p.getId());//輸出打印:1
System.out.println(Objects.requireNonNull(softReference.get()).getId());//輸出打印:1
//斷開p和Obj的強引用
p = null;
//System.out.println(p.getId());
//System.gc();
System.out.println(Objects.requireNonNull(softReference.get()).getId());//輸出打印:1
//並不報空指針異常 雖然斷開了p和Obj的強引用,但是並沒有被回收.
//如果在前面調用gc() 垃圾回收 運行結果也是打印1的..軟引用只有系統在發生內存溢出異常之前,會把只被軟引用的對象進行回收
}
/**
* 弱引用
*/
@Test
public void test2() {
//在堆中創建一個對象Obj
//在棧中創建一個p來強引用此對象Obj
Person p = new Person(1);
//在棧中創建一個weakReference來弱引用此對象Obj 可以獲取對象的屬性值
WeakReference<Person> weakReference = new WeakReference<Person>(p);
System.out.println(Objects.requireNonNull(weakReference.get()).getId());//打印輸出:1
//斷開p和Obj的強引用
p = null;
//System.gc();
System.out.println(Objects.requireNonNull(weakReference.get()).getId());//打印輸出:1
//p=null 之後 還是可以正常的打印輸出1 說明斷開強引用和其他弱引用,軟引用壓根沒有關係.
//如果在打印之前 調用gc() 方法之後 就會報錯..java.lang.NullPointerException
//垃圾回收不論內存是否不足都會回收只被弱引用關聯的對象。
}
/**
* 虛引用: 1
*/
@Test
public void test3() {
//在堆中創建一個對象Obj
//在棧中創建一個p來強引用此對象Obj
Person p = new Person(1);
//Phantom 幻影幽靈 的意思
ReferenceQueue<Person> referenceQueue = new ReferenceQueue<Person>();
//在棧中創建一個phantomReference來虛引用此對象Obj 不可以獲取對象的屬性值
PhantomReference<Person> phantomReference = new PhantomReference<Person>(p, referenceQueue);
System.out.println(Objects.requireNonNull(phantomReference.get()).getId());//打印報錯 java.lang.NullPointerException
//直接得不到p對象對應的id值....
//PhantomReference的唯一作用就是 能在這個對象被收集器回收時收到一個系統通知 看test4()方法
}
/**
* 虛引用: 2
*/
@Test
public void test4() {
//在堆中創建一個對象Obj
//在棧中創建一個p來強引用此對象Obj
Person p = new Person(1);
//Phantom 幻影幽靈 的意思
ReferenceQueue<Person> referenceQueue = new ReferenceQueue<Person>();
//在棧中創建一個phantomReference來虛引用此對象Obj 不可以獲取對象的屬性值
PhantomReference<Person> phantomReference = new PhantomReference<Person>(p, referenceQueue);
System.out.println(referenceQueue.poll());//打印輸出: null 這個是查詢隊列中是否有元素.
//斷開p和obj的強引用
p = null;
System.gc();//p被回收之後 隊列referenceQueue中就有值了.
System.runFinalization(); // 強制調用已經失去引用的對象的finalize方法
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}//過 一秒鐘之後再查詢隊列中是否有元素.
System.out.println(referenceQueue.poll());//打印輸出: java.lang.ref.PhantomReference@77fef1a0
//PhantomReference的唯一作用就是 能在這個對象被收集器回收時收到一個系統通知
//如果這個對象被回收了,會把通知放到隊列中.
//如果前面p=null註釋掉 再運行打印輸出就是 null 因爲p沒有被回收(強引用中) 就不會把通知放到隊列中...隊列中爲空 null
//回收的標誌就是把通知放到隊列中..
}
}