Java中的四種引用類型
微信公衆號【Java技術江湖】一位阿里 Java 工程師的技術小站。作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!(關注公衆號後回覆”Java“即可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送作者原創的Java學習指南、Java程序員面試指南等乾貨資源)
一、背景
Java的內存回收不需要程序員負責,JVM會在必要時啓動Java GC完成垃圾回收。Java以便我們控制對象的生存週期,提供給了我們四種引用方式,引用強度從強到弱分別爲:強引用、軟引用、弱引用、虛引用。
二、簡介
1.強引用 StrongReference
StrongReference是Java的默認引用形式,使用時不需要顯示定義。任何通過強引用所使用的對象不管系統資源有多緊張,Java GC都不會主動回收具有強引用的對象。
public class StrongReferenceTest { public static int M = 1024*1024; public static void printlnMemory(String tag){ Runtime runtime = Runtime.getRuntime(); int M = StrongReferenceTest.M; System.out.println("\n"+tag+":"); System.out.println(runtime.freeMemory()/M+"M(free)/" + runtime.totalMemory()/M+"M(total)"); } public static void main(String[] args){ StrongReferenceTest.printlnMemory("1.原可用內存和總內存"); //實例化10M的數組並與strongReference建立強引用 byte[] strongReference = new byte[10*StrongReferenceTest.M]; StrongReferenceTest.printlnMemory("2.實例化10M的數組,並建立強引用"); System.out.println("strongReference : "+strongReference); System.gc(); StrongReferenceTest.printlnMemory("3.GC後"); System.out.println("strongReference : "+strongReference); //strongReference = null;後,強引用斷開了 strongReference = null; StrongReferenceTest.printlnMemory("4.強引用斷開後"); System.out.println("strongReference : "+strongReference); System.gc(); StrongReferenceTest.printlnMemory("5.GC後"); System.out.println("strongReference : "+strongReference); } }
運行結果:
2.弱引用 WeakReference
如果一個對象只具有弱引用,無論內存充足與否,Java GC後對象如果只有弱引用將會被自動回收。
public class WeakReferenceTest { public static int M = 1024*1024; public static void printlnMemory(String tag){ Runtime runtime = Runtime.getRuntime(); int M = WeakReferenceTest.M; System.out.println("\n"+tag+":"); System.out.println(runtime.freeMemory()/M+"M(free)/" + runtime.totalMemory()/M+"M(total)"); } public static void main(String[] args){ WeakReferenceTest.printlnMemory("1.原可用內存和總內存"); //創建弱引用 WeakReference<Object> weakRerference = new WeakReference<Object>(new byte[10*WeakReferenceTest.M]); WeakReferenceTest.printlnMemory("2.實例化10M的數組,並建立弱引用"); System.out.println("weakRerference.get() : "+weakRerference.get()); System.gc(); StrongReferenceTest.printlnMemory("3.GC後"); System.out.println("weakRerference.get() : "+weakRerference.get()); } }
運行結果:
3.軟引用 SoftReference
軟引用和弱引用的特性基本一致, 主要的區別在於軟引用在內存不足時纔會被回收。如果一個對象只具有軟引用,Java GC在內存充足的時候不會回收它,內存不足時纔會被回收。
public class SoftReferenceTest { public static int M = 1024*1024; public static void printlnMemory(String tag){ Runtime runtime = Runtime.getRuntime(); int M = StrongReferenceTest.M; System.out.println("\n"+tag+":"); System.out.println(runtime.freeMemory()/M+"M(free)/" + runtime.totalMemory()/M+"M(total)"); } public static void main(String[] args){ SoftReferenceTest.printlnMemory("1.原可用內存和總內存"); //建立軟引用 SoftReference<Object> softRerference = new SoftReference<Object>(new byte[10*SoftReferenceTest.M]); SoftReferenceTest.printlnMemory("2.實例化10M的數組,並建立軟引用"); System.out.println("softRerference.get() : "+softRerference.get()); System.gc(); SoftReferenceTest.printlnMemory("3.內存可用容量充足,GC後"); System.out.println("softRerference.get() : "+softRerference.get()); //實例化一個4M的數組,使內存不夠用,並建立軟引用 //free=10M=4M+10M-4M,證明內存可用量不足時,GC後byte[10*m]被回收 SoftReference<Object> softRerference2 = new SoftReference<Object>(new byte[4*SoftReferenceTest.M]); SoftReferenceTest.printlnMemory("4.實例化一個4M的數組後"); System.out.println("softRerference.get() : "+softRerference.get()); System.out.println("softRerference2.get() : "+softRerference2.get()); } }
運行結果:
4.虛引用 PhantomReference
從PhantomReference類的源代碼可以知道,它的get()方法無論何時返回的都只會是null。所以單獨使用虛引用時,沒有什麼意義,需要和引用隊列ReferenceQueue類聯合使用。當執行Java GC時如果一個對象只有虛引用,就會把這個對象加入到與之關聯的ReferenceQueue中。
public class PhantomReferenceTest { public static int M = 1024*1024; public static void printlnMemory(String tag){ Runtime runtime = Runtime.getRuntime(); int M = PhantomReferenceTest.M; System.out.println("\n"+tag+":"); System.out.println(runtime.freeMemory()/M+"M(free)/" + runtime.totalMemory()/M+"M(total)"); } public static void main(String[] args) throws InterruptedException { PhantomReferenceTest.printlnMemory("1.原可用內存和總內存"); byte[] object = new byte[10*PhantomReferenceTest.M]; PhantomReferenceTest.printlnMemory("2.實例化10M的數組後"); //建立虛引用 ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>(); PhantomReference<Object> phantomReference = new PhantomReference<Object>(object,referenceQueue); PhantomReferenceTest.printlnMemory("3.建立虛引用後"); System.out.println("phantomReference : "+phantomReference); System.out.println("phantomReference.get() : "+phantomReference.get()); System.out.println("referenceQueue.poll() : "+referenceQueue.poll()); //斷開byte[10*PhantomReferenceTest.M]的強引用 object = null; PhantomReferenceTest.printlnMemory("4.執行object = null;強引用斷開後"); System.gc(); PhantomReferenceTest.printlnMemory("5.GC後"); System.out.println("phantomReference : "+phantomReference); System.out.println("phantomReference.get() : "+phantomReference.get()); System.out.println("referenceQueue.poll() : "+referenceQueue.poll()); //斷開虛引用 phantomReference = null; System.gc(); PhantomReferenceTest.printlnMemory("6.斷開虛引用後GC"); System.out.println("phantomReference : "+phantomReference); System.out.println("referenceQueue.poll() : "+referenceQueue.poll()); } }
運行結果:
三、小結
強引用是 Java 的默認引用形式,使用時不需要顯示定義,是我們平時最常使用到的引用方式。不管系統資源有多緊張,Java GC都不會主動回收具有強引用的對象。 弱引用和軟引用一般在引用對象爲非必需對象的時候使用。它們的區別是被弱引用關聯的對象在垃圾回收時總是會被回收,被軟引用關聯的對象只有在內存不足時纔會被回收。 虛引用的get()方法獲取的永遠是null,無法獲取對象實例。Java GC會把虛引用的對象放到引用隊列裏面。可用來在對象被回收時做額外的一些資源清理或事物回滾等處理。 由於無法從虛引獲取到引用對象的實例。它的使用情況比較特別,所以這裏不把虛引用放入表格進行對比。這裏對強引用、弱引用、軟引用進行對比:
引用類型 | GC時JVM內存充足 | GC時JVM內存不足 |
---|---|---|
強引用 | 不被回收 | 不被回收 |
弱引用 | 被回收 | 被回收 |
軟引用 |