總體結構
java提供了4中引用類型,在垃圾回收的時候,都有本身的各自特色。markdown
爲何要區分這麼多引用呢,其實這和Java的GC有密切關係。jvm
強引用(默認支持模式)
- 把一個對象賦給一個引用變量,這個引用變量就是一個強引用。
- 強引用是咱們最多見的普通對象引用,只要還有強引用指向一個對象,就能代表對象還活着
- 當內存不足的時候,jvm開始垃圾回收,對於強引用的對象,就算出現OOM也不會回收該對象的。
所以,強引用是形成java內存泄露的主要緣由之一。 - 對於一個普通的對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯示的將引用賦值爲null,GC就會回收這個對象了。
案例
public static void main(String[] args) { Object obj=new Object();//這樣定義就是一個強引用 Object obj2=obj;//也是一個強引用 obj=null; System.gc(); //不會被垃圾回收 System.out.println(obj2); }
軟引用(SoftReference)
- 軟引用是一種相對強化引用弱化了一些引用,須要使用java.lang.SoftReference類來實現。
- 對於只有軟引用的對象來講,
當系統內存充足時,不會被回收;
當系統內存不足時,會被回收;
案例
/** * jvm配置配置小的內存,故意產生大的對象,致使OOM, * 驗證軟引用在內存足夠的先後是否被回收。 * 參數:-Xms:5M -Xmx:5M * @param args */ public static void main(String[] args) { Object obj=new Object();//這樣定義就是一個強引用 //軟引用須要使用java.lang.SoftReference來實現 //如今sf就是一個軟引用 SoftReference sf=new SoftReference(obj); obj=null; System.out.println("內存足夠軟引用引用的對象"+sf.get()); try { final byte[] bytes = new byte[8 * 1024 * 1024]; } catch (Exception e) { e.printStackTrace(); }finally { System.out.println("內存不夠:軟引用引用的對象:"+sf.get()); } }
結果:
ide
弱引用
- 弱引用須要用java.lang.WeakReference類來實現,它比軟引用的生存期更短。
* 若是一個對象只是被弱引用引用者,那麼只要發生GC,無論內存空間是否足夠,都會回收該對象。函數
- ThreadLocal靜態內部類ThreadLocalMap中的Entiry中的key就是一個虛引用;
案例
public static void main(String[] args) { Object obj=new Object(); WeakReference wrf=new WeakReference(obj); obj=null; System.out.println("未發生GC以前"+wrf.get()); System.gc(); System.out.println("內存充足,發生GC以後"+wrf.get()); }
結果:post
未發生GC以前java.lang.Object@2d363fb3
內存充足,發生GC以後null
你知道弱引用的話,能談談WeakHashMap嗎?
WeakHashMap的鍵是「弱鍵」,也就是鍵的引用是一個弱引用。性能
public static void main(String[] args) { WeakHashMap<String,Integer> map=new WeakHashMap<>(); String key = new String("wekHashMap"); map.put(key,1); key=null; System.gc(); System.out.println(map); }
結果:map爲空了。
理論上咱們只是把引用變量key變成null了,"wekHashMap"字符串應該被Map中key引用啊,不該該被GC回收啊,
可是由於key是弱引用,GC回收的時候就忽略了這個引用,把對象當成垃圾收回了。url
虛引用
- 虛引用須要 java. langref.PhantomReference類來實現。
- 顧名思義,就是形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。
若是一個對象僅被虛引用持有,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。 - 它不能單獨使用也不能經過它訪問對象,虛引用必須和引用隊列( Reference queue)聯合使用。
- 虛引用的主要做用是跟蹤對象被垃圾回收的狀態。僅僅是提供了一種確保對象被 finalize之後,作某些事情的機制。
- PhantomReference的get方法老是返回null,所以沒法訪問對應的引用對象。
使用它的意義在於說明一個對象已經進入 finalization階段,能夠被回收,用來實現比 finalization機制更靈活的回收操做
換句話說,設置虛引用關聯的惟一目的,就是在這個對象被收集器回收的時候收到一個系統通知或者後續添加進一步的處理;
ReferenceQueue 引用隊列
- 對象在被回收以前要被引用隊列保存一下。GC以前對象不放在隊列中,GC以後纔對象放入隊列中。
- 【經過開啓線程監聽該引用隊列的變化狀況】就能夠在對象被回收時採起相應的動做。
因爲虛引用的惟一目的就是能在這個對象被垃圾收集器回收時能收到系統通知,於是建立虛引用時必需要關聯一個引用隊列,而軟引用和弱引用則不是必須的。
這裏所謂的收到系統通知其實仍是經過開啓線程監聽該引用隊列的變化狀況來實現的。 - 這裏還須要強調的是,
對於軟引用和弱引用,當執行第一次垃圾回收時,就會將軟引用或弱引用對象添加到其關聯的引用隊列中,而後其finalize函數纔會被執行(若是沒複寫則不會被執行);
而對於虛引用,若是被引用對象沒有複寫finalize方法,則是在第一垃圾回收將該類銷燬以後,纔會將虛擬引用對象添加到引用隊列,
若是被引用對象複寫了finalize方法,則是當執行完第二次垃圾回收以後,纔會將虛引用對象添加到其關聯的引用隊列
class User{ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("我要被GC幹了!"); } } public static void main(String[] args) throws Exception { User user=new User(); ReferenceQueue<User> queue=new ReferenceQueue(); PhantomReference prf=new PhantomReference(user,queue); //啓動一個線程監控引用隊列的變化 new Thread(()->{ for(;;){ final Reference<? extends User> u = queue.poll(); if (u!=null){ System.out.println("有對象被加入到了引用隊列了!"+u); } } }).start(); user=null; //GC以前引用隊列爲空 System.out.println("GC以前"+queue.poll()); System.gc(); Thread.sleep(100); //GC以後引用隊列纔將對象放入 System.out.println("第一次GC以後"+queue.poll()); System.gc(); Thread.sleep(100); System.out.println("第二次GC以後"+queue.poll()); }
結果:
GC以前null
我要被GC幹了!
第一次GC以後null
有對象被加入到了引用隊列了!java.lang.ref.PhantomReference@549763fd
第二次GC以後java.lang.ref.PhantomReference@5aaa6d82
應用場景
軟引用:SoftReference的應用場景
假若有一個應用須要讀取大量的本地圖片
每次讀取圖片都從硬盤讀取會影響性能。
一次所有加載到內存中,又可能形成內存溢出。
此時,可使用軟引用解決問題;
使用一個HashMap保存圖片的路徑和響應圖片對象關聯的軟引用之間的映射關係,
內存不足時,jvm會自動回收這些緩存圖片對象所佔用的空間,能夠避免OOM。
Map<String,SoftReference<Bigmap>> imageCache=new HashMap<String,SoftReference<Bitmap>>();