Java的四種引用之強弱軟虛

 在java中提供4個級別的引用:強引用、軟引用、弱引用和虛引用。除了強引用外,其餘3中引用都可以在java.lang.ref包中找到對應的類。開發人員能夠在應用程序中直接使用他們。java

1 強引用緩存

強引用就是程序中通常使用的引用類型,強引用的對象是可觸及的,不會被回收。相對的,軟引用、弱引用和虛引用的對象是軟可觸及的、弱可觸及的和虛可觸及的,在必定條件下,都是能夠被回收的。jvm

強引用示例:StringBuffer str = new StringBuffer("Hello world");ide

假設以上代碼是在函數體內運行的,那麼局部變量str將被分配在棧上,而對象StringBuffer實例被分配在堆上。局部變量str指向StringBuffer實例所在堆空間,經過str能夠操做該實例,那麼str就是StringBuffer實例的強引用。函數

此時,若是再運行一個賦值語句:this

StringBuffer str1 = str;線程

則str所指向的對象也將被str1所指向,同時在局部變量表上會分配空間存放str1變量。此時該StringBuffer實例就有兩個引用。對引用的「==」操做用於表示兩操做數所指向的堆空間地址是否相同,不表示兩操做數所指的對象是否相同。對象

強引用的特色:blog

  • 強引用能夠直接訪問目標對象
  • 強引用所指向的對象在任什麼時候候都不會被系統回收,虛擬機寧願拋出OOM異常,也不會回收強引用所指的對象。
  • 強引用可能致使內存泄漏

 

2 軟引用----可被回收的引用隊列

軟引用是比強引用弱一點的引用類型,一個對象只持有軟引用,那麼當堆空間不足時,就會被回收。軟引用使用java.lang.ref.SoftReference類型

package com.jvm;
import java.lang.ref.SoftReference;
public class SoftRef {
  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="+String.valueOf(id)+",name="+name+"]";
    }
  }
  public static void main(String[] args) {
    User u = new User(1,"gim");
    SoftReference<User> userSoftRef = new SoftReference<User>(u);//建立引用給定對象的新的軟引用
    u = null; //去除強引用

    System.out.println(userSoftRef.get());
    System.gc();
    System.out.println("After GC:");
    System.out.println(userSoftRef.get());

    byte[] b = new byte[1024*925*7];//分配7M左右,使內存空間緊張
    System.gc();
    System.out.println(userSoftRef.get());
  }
}

若是虛擬機不加參數設置堆內存限制,則打印出:

[id=1,name=gim]
After GC:
[id=1,name=gim]
[id=1,name=gim]

由於虛擬機的內存總量是九十多兆,且虛擬機企圖使用的最大內存總量是這個的十幾倍。故不會出現資源緊張,也不會產生內存溢出現象。

若是使用-Xmx10m運行上述代碼,能夠獲得:

[id=1,name=gim](第一次從軟引用中獲取數據)
After GC:
[id=1,name=gim](GC沒有清除軟引用)
null(因爲內存緊張,軟引用被清除)

結論:GC未必會回收軟引用的對象,可是當內存資源緊張時,軟引用對象會被回收,因此軟引用對象不會引發內存溢出。

 

3 弱引用-----發現即回收

 弱引用是一種比軟引用較弱的引用類型。在系統GC時,只要發現弱引用,無論系統堆空間使用狀況如何,都會將對象進行回收。可是因爲垃圾回收器的優先級一般很低,所以,並不必定能很快的發現持有弱引用的對象。在這種狀況下,弱引用對象能夠存在較長的時間,一旦一個弱引用對象被垃圾回收器回收,便會加入一個註冊的引用隊列(這一點和軟引用很像)。弱引用使用java.lang.ref.WeakReference類實現。

實例2:展現弱引用的特色

package com.jvm;
import java.lang.ref.WeakReference;
public class WeakRef {
  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="+String.valueOf(id)+",name="+name+"]";
    }
  }

  public static void main(String[] args) {
    User u = new User(1,"gim");
    WeakReference<User> userReference = new WeakReference<User>(u);
    u = null;
    System.out.println(userReference.get());
    System.gc();
    System.out.println("After GC:");
    System.out.println(userReference.get());
  }
}

不用設置虛擬機參數,直接運行代碼打印出:

[id=1,name=gim]
After GC:
null

能夠看書,GC以後,弱引用對象當即被清除。弱引用和軟引用同樣,在構造弱引用時,也能夠指定一個引用隊列,當弱引用對象被回收時,就會加入指定的引用隊列,經過這個隊列能夠跟蹤對象的回收狀況。

注意:軟引用、弱引用都很是適合來保存那些無關緊要的緩存數據(應用場景)。若是這麼作,當系統內存不足時,這些緩存數據會被回收,不會致使內存的溢出,而當內存資源充足時,這些緩存又能夠存在至關長的時間,從而起到加速系統的做用。

 

4 虛引用----對象回收跟蹤

 一個持有虛引用的對象,和沒有引用幾乎同樣。隨時均可能被垃圾回收器回收,當試圖經過虛引用的get()方法取得強引用時,老是會失敗。

而且,虛引用必須和引用隊列一塊兒使用,它的做用主要是跟蹤垃圾回收過程

當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象後,將這個虛引用加入引用隊列,以通知引用程序對象的回收狀況。

實例3 :使用虛引用跟蹤一個可復活對象的回收

package com.jvm;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class TraceCanReliveObj {
  public static TraceCanReliveObj obj;  //能夠復活對象
  static ReferenceQueue<TraceCanReliveObj> phantomQueue = null;
  public static class CheckRefQueue extends Thread{
    @Override
    public void run() {
      while(true){
        if(phantomQueue!=null){
          PhantomReference<TraceCanReliveObj> objt = null;
          try{
            objt = (PhantomReference<TraceCanReliveObj>) phantomQueue.remove();
          }catch(InterruptedException e){
            e.printStackTrace();
          }
          if(objt!=null){
            System.out.println("TraceCanReliveObj is delete by GC");
          }
        }
      }
    }
  }

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    System.out.println("CanReliveObj finalize called");
    obj = this; //使得對象復活
  }
  @Override
  public String toString() {
    return "I am CanReliveObj";
  }
  public static void main(String[] args) throws InterruptedException {
    Thread t = new CheckRefQueue();
    t.setDaemon(true);//守護線程
    t.start();
    phantomQueue = new ReferenceQueue<TraceCanReliveObj>();
    obj = new TraceCanReliveObj();

    //構造了TraceCanReliveObj對象的虛引用,並指定了引用隊列。
    PhantomReference<TraceCanReliveObj> phantomRef = new PhantomReference<TraceCanReliveObj>(obj, phantomQueue);
    obj = null;  //將強引用去除
    System.gc();  //第一次進行GC,因爲對象可復活,GC沒法回收該對象。
    Thread.sleep(1000);
    if(obj==null){
      System.out.println("obj 是 null");
    }else{
      System.out.println("obj 可用");
    }
    System.out.println("第二次GC");
    obj = null;
    System.gc(); //第二次進行GC,因爲finalize()只會被調用一次,所以第2次GC會回收對象,同時其引用隊列應該也會捕獲到對象的回收
    Thread.sleep(1000);
    if(obj==null){
      System.out.println("obj 是 null");
    }else{
      System.out.println("obj 可用");
    }
  }
}

直接運行代碼打印:

CanReliveObj finalize called    (對象復活)
obj 可用
第二次GC           (第2次,對象沒法復活) 
TraceCanReliveObj is delete by GC (引用隊列捕獲到對象被回收)
obj 是 null

因爲虛引用能夠跟蹤對象的回收時間,所以,也能夠將一些資源釋放操做放置在虛引用中執行和記錄。

相關文章
相關標籤/搜索