JVM引用
- 我們希望能描述這樣一類對象: 當內存空間還足夠時,則能保留在內存中;如果內存空間在進行垃圾收集后還是很緊張,則可以拋棄這些對象。 -【既偏門又非常高頻的面試題】強引用、軟引用、弱引用、虛引用有什么區別?具體使用.場景是什么?
- 在JDK 1.2版之后,Java對引用的概念進行了擴充,將引用分為強引用(Strong Reference)、軟引用(Soft Reference) 、弱引用(Weak Reference) 和虛引用(Phantom Reference) 4種,這4種引用強度依次逐漸減弱。
- 除強引用外,其他3種引用均可以在java.lang.ref包中找到它們的身影。如下圖,顯示了這3種引用類型對應的類,開發人員可以在應用程序中直接使用它們。
Reference子類中只有終結器引用是包內可見的(該類沒有被public修飾),其他3種引用類型均為public class,可以在應用程序中直接使用。
對於強、軟、弱、虛這四種引用對象的垃圾回收特點的描述,都是指的在引用關系還存在的情況下:
- 強引用(StrongReference):最傳統的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“0bject obj=new object( )”這種引用關系。無論任何情況下,只要強引用關系還存在,垃圾收集器就永遠不會回收掉被引用的對象。
- 軟引用(SoftReference) :在系統將要發生內存溢出之前,會把這些對象列入回收范圍之中,以備進行第二次(第一次指的是回收了不可觸及的垃圾對象)垃圾回收的時候回收它們。如果這次回收后還沒有足夠的內存,才會拋出內存溢出異常。
- 弱引用(WeakReference) :被弱引用關聯的對象只能生存到下一次垃圾收集之前。當垃圾收集器工作時,無論內存空間是否足夠,都會回收掉被弱引用關聯的對象。
- 虛引用(PhantomReference) :一個對象是否有虛引用的存在,完全不會對其生存時 間構成影響,也無法通過虛引用來獲得一個對象的實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知(回收跟蹤)。
強引用: 不回收
- 在Java程序中,最常見的引用類型是強引用(普通系統99%以上都是強引用),也就是我們最常見的普通對象引用,也是默認的引用類型。
- 當在Java語言中使用new操作符創建一個新的對象, 並將其賦值給一個變量的時候,這個變量就成為指向該對象的一個強引用。
- 強引用的對象是可觸及的(可達的),垃圾收集器就永遠不會回收掉被引用的對象。
- 對於一個普通的對象,如果沒有其他的引用關系,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為null,才可以當做垃圾被收集,當然具體回收時機還是要看垃圾收集策略。
- 相對的,軟引用、 弱引用和虛引用的對象是軟可觸及、弱可觸及和虛可觸及的,在一定條件下,都是可以被回收的。所以,強引用是造成Java內存泄漏的主要原因之一。
public class StrongReferenceTest { public static void main(String[] args) { StringBuffer str = new StringBuffer ("Hello,尚硅谷"); StringBuffer str1 = str; str = null; System.gc(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(str1); } }
StringBuffer str = new StringBuffer ("Hello,尚硅谷");
局部變量str指向StringBuffer實例所在堆空間,通過str可以操作該實例,那么str就是StringBuffer實例的強引用。
對應內存結構:

此時,如果再運行一個賦值語句:StringBuffer str1 = str;
對應內存結構:

本例中的兩個引用,都是強引用,強引用具備以下特點:
- 強引用可以直接訪問目標對象。
- 強引用所指向的對象在任何時候都不會被系統回收,虛擬機寧願拋出OOM異常,也不會回收強引用所指向對象。
- 強引用可能導致內存泄漏。
軟引用: 內存不足即回收
- 軟引用是用來描述一 些還有用,但非必需的對象。只被軟引用關聯着的對象,在系統將要發生內存溢出異常前,會把這些對象列進回收范圍之中進行第二次回收,如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。
- 軟引用通常用來實現內存敏感的緩存。比如:高速緩存就有用到軟引用。如果還有空閑內存,就可以暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。
- 垃圾回收器在某個時刻決定回收軟可達的對象的時候,會清理軟引用,並可選地把引用存放到一個引用隊列( Reference Queue)。
- 類似弱引用,只不過Java虛擬機會盡量讓軟引用的存活時間長一些,迫不得.已才清理。
- 軟引用:
- 當內存足夠: 不會回收軟引|用的可達對象
- 當內存不夠時: 會回收軟引用的可達對象
- 在JDK 1. 2版之后提供了java.lang.ref.SoftReference類來實現軟引用。
Object obj = new object(); //聲明強引用 SoftReference<0bject> sf = new SoftReference<0bject>(obj);//創建軟引用 obj = null; //銷毀強引用
測試代碼:
/** * 軟引用的測試:內存不足即回收 * -Xms10m -Xmx10m -XX:+PrintGCDetails */ public class SoftReferenceTest { public static class User { public User(int id, String name) { this.id = id; this.name = name; } public int id; public String name; @Override public String toString() { return "[id=" + id + ", name=" + name + "] "; } } public static void main(String[] args) { //創建對象,建立軟引用 // SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "songhk")); //上面的一行代碼,等價於如下的三行代碼 User u1 = new User(1,"songhk"); SoftReference<User> userSoftRef = new SoftReference<User>(u1); u1 = null;//取消強引用 //從軟引用中重新獲得強引用對象 System.out.println(userSoftRef.get()); System.gc(); System.out.println("After GC:"); // //垃圾回收之后獲得軟引用中的對象 System.out.println(userSoftRef.get());//由於堆空間內存足夠,所有不會回收軟引用的可達對象。 // try { //讓系統認為內存資源緊張、不夠 // byte[] b = new byte[1024 * 1024 * 7]; byte[] b = new byte[1024 * 7168 - 399 * 1024];//恰好能放下數組又放不下u1的內存分配大小 不會報OOM } catch (Throwable e) { e.printStackTrace(); } finally { //再次從軟引用中獲取數據 System.out.println(userSoftRef.get());//在報OOM之前,垃圾回收器會回收軟引用的可達對象。打印為null } } }
弱引用: 發現即回收
- 弱引用也是用來描述那些非必需對象,被弱引用關聯的對象只能生存到下一次垃圾收集發生為止。在系統GC時,只要發現弱引用,不管系統堆空間使用是否充足,都會回收掉只被弱引用關聯的對象。
- 但是,由於垃圾回收器的線程通常優先級很低,因此,並不一 定能很快地發現持有弱引用的對象。在這種情況下,弱引用對象可以存在較長的時間。
- 弱引用和軟引用一樣,在構造弱引用時,也可以指定一個引用隊列,就會加入指定的引用隊列,當弱引用對象被回收時,通過這個隊列可以跟蹤對象的回收情況。
- 軟引用、弱引用都非常適合來保存那些可有可無的緩存數據。如果這么做,當系統內存不足時,這些緩存數據會被回收,不會導致內存溢出。而當內存資源充足時,這些緩存數據又可以存在相當長的時間,從而起到加速系統的作用。
- 在JDK1.2版之后提后了java.lang.ref.WeakReference類來實現弱引用
Object obj = new object(); //聲明強引用 WeakReference<0bject> sf = new WeakReference<0bject>(obj);//創建弱引用 obj = null; //銷毀強引用
- 弱引用對象與軟引用對象的最大不同就在於,當GC在進行回收時,需要通過算法檢查是否回收軟引用對象,而對於弱引用對象,GC總是進行回收。弱引用對象更容易、更快被GC回收。
面試題:你開發中使用過WeakHashMap嗎?
- 通過查看WeakHashMap源碼,可以看到其內部類Entry使用的就是弱引用
- line 702 ->
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {...}
line 702 -> private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {...}
示例代碼:
public class WeakReferenceTest { public static class User { public User(int id, String name) { this.id = id; this.name = name; } public int id; public String name; @Override public String toString() { return "[id=" + id + ", name=" + name + "] "; } } public static void main(String[] args) { //構造了弱引用 WeakReference<User> userWeakRef = new WeakReference<User>(new User(1, "songhk")); //從弱引用中重新獲取對象 System.out.println(userWeakRef.get()); System.gc(); // 不管當前內存空間足夠與否,都會回收它的內存 System.out.println("After GC:"); //重新嘗試從弱引用中獲取對象 System.out.println(userWeakRef.get());//null } }
虛引用: 對象回收跟蹤
- 虛引用(Phantom Reference),也稱為“幽靈引用”或者“幻影引用”,是所有引用類型中最弱的一個。
- 一個對象是否有虛引用的存在,完全不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它和沒有引用幾乎是一樣的,隨時都可能被垃圾回收器回收。
- 它不能單獨使用,也無法通過虛引用來獲取被引用的對象。當試圖通過虛引用的get()方法取得對象時,總是null。
- 為一個對象設置虛引用關聯的唯一目的在於跟蹤垃圾回收過程。比如:能在這個對象被收集器回收時收到一個系統通知。
- 虛引用必須和引用隊列一起使用。虛引用在創建時必須提供一個引用隊列作為參數。當垃圾回收器准備回收一個對象時,如果發現它還有虛引用,就會在回收對象后,將這個虛引用加入引用隊列,以通知應用程序對象的回收情況。
- 由於虛引用可以跟蹤對象的回收時間,因此,也可以將一些資源釋放操作放置在虛引用中執行和記錄。
- 在JDK 1. 2版之后提供了PhantomReference類來實現虛引用。
object obj = new object(); ReferenceQueue phantomQueue = new ReferenceQueue( ) ; PhantomReference<object> pf = new PhantomReference<object>(obj, phantomQueue); obj = null;
測試代碼:
public class PhantomReferenceTest { public static PhantomReferenceTest obj;//當前類對象的聲明 static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//引用隊列 public static class CheckRefQueue extends Thread { @Override public void run() { while (true) { if (phantomQueue != null) { PhantomReference<PhantomReferenceTest> objt = null; try { objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove(); } catch (InterruptedException e) { e.printStackTrace(); } if (objt != null) { System.out.println("追蹤垃圾回收過程:從隊列當中取出了objt,證明之前的PhantomReferenceTest實例被GC了"); } } } } } @Override protected void finalize() throws Throwable { //finalize()方法只能被調用一次! super.finalize(); System.out.println("調用當前類的finalize()方法"); obj = this; } public static void main(String[] args) { Thread t = new CheckRefQueue(); t.setDaemon(true);//設置為守護線程:當程序中沒有非守護線程時,守護線程也就執行結束。 t.start(); phantomQueue = new ReferenceQueue<PhantomReferenceTest>(); obj = new PhantomReferenceTest(); //構造了 PhantomReferenceTest 對象的虛引用,並指定了引用隊列 PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue); try { //不可獲取虛引用中的對象 System.out.println(phantomRef.get()); //將強引用去除 obj = null; //第一次進行GC,由於對象在finalize()重寫了可復活,GC無法回收該對象 System.gc(); Thread.sleep(1000); if (obj == null) { System.out.println("obj 是 null"); } else { System.out.println("obj 可用"); } System.out.println("第 2 次 gc"); obj = null; System.gc(); //一旦將obj對象回收,就會將此虛引用存放到引用隊列中。finalize()只會執行一次,不會再復活了 Thread.sleep(1000); if (obj == null) { System.out.println("obj 是 null"); } else { System.out.println("obj 可用"); } } catch (InterruptedException e) { e.printStackTrace(); } } }
輸出:
null 調用當前類的finalize()方法 obj 可用 第 2 次 gc
追蹤垃圾回收過程:從隊列當中取出了objt,證明之前的PhantomReferenceTest實例被GC了
obj 是 null
終結器引用
- 它用以實現對象的finalize()方法,也可以稱為終結器引用。
- 無需手動編碼, 其內部配合引用隊列使用。
- 在GC時, 終結器引用入隊。由Finalizer線程通過終結器引用找到被引用對象並調用它的finalize()方法,第二次GC時才能回收被引用對象。
