談談Java對象的強引用,軟引用,弱引用,虛引用分別是什么


整體結構

java提供了4中引用類型,在垃圾回收的時候,都有自己的各自特點。

為什么要區分這么多引用呢,其實這和Java的GC有密切關系。

強引用(默認支持模式)

  • 把一個對象賦給一個引用變量,這個引用變量就是一個強引用。
  • 強引用是我們最常見的普通對象引用,只要還有強引用指向一個對象,就能表明對象還活着
  • 當內存不足的時候,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());
        }

    }

結果:

弱引用

  • 弱引用需要用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());
    }

結果:

未發生GC之前java.lang.Object@2d363fb3
內存充足,發生GC之后null

ThreadLocal

你知道弱引用的話,能談談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回收的時候就忽略了這個引用,把對象當成垃圾收回了。

虛引用

  • 虛引用需要 java. langref.PhantomReference類來實現。
  • 顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命周期。
    如果一個對象僅被虛引用持有,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
  • 它不能單獨使用也不能通過它訪問對象,虛引用必須和引用隊列( Reference queue)聯合使用。
  • 虛引用的主要作用是跟蹤對象被垃圾回收的狀態。僅僅是提供了一種確保對象被 finalize以后,做某些事情的機制。
  • PhantomReference的get方法總是返回null,因此無法訪問對應的引用對象。
    使用它的意義在於說明一個對象已經進入 finalization階段,可以被回收,用來實現比 finalization機制更靈活的回收操作
    換句話說,設置虛引用關聯的唯一目的,就是在這個對象被收集器回收的時候收到一個系統通知或者后續添加進一步的處理;
  • 虛引用用來管理堆外內存

ReferenceQueue 引用隊列

  • 對象在被回收之前要被引用隊列保存一下。GC之前對象不放在隊列中,GC之后才對象放入隊列中。
  • 【通過開啟線程監聽該引用隊列的變化情況】就可以在對象被回收時采取相應的動作。
    由於虛引用的唯一目的就是能在這個對象被垃圾收集器回收時能收到系統通知,因而創建虛引用時必須要關聯一個引用隊列,而軟引用和弱引用則不是必須的。
    這里所謂的收到系統通知其實還是通過開啟線程監聽該引用隊列的變化情況來實現的。
  • 這里還需要強調的是,
    對於軟引用和弱引用,當執行第一次垃圾回收時,就會將軟引用或弱引用對象添加到其關聯的引用隊列中,然后其finalize函數才會被執行(如果沒復寫則不會被執行);
    而對於虛引用,如果被引用對象沒有復寫finalize方法,則是在第一垃圾回收將該類銷毀之后,才會將虛擬引用對象添加到引用隊列,
    如果被引用對象復寫了finalize方法,則是當執行完第二次垃圾回收之后,才會將虛引用對象添加到其關聯的引用隊列
  • 一個對象的finalize()方法只會被調用一次,而且finalize()被調用不意味着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>>();

https://www.bilibili.com/video/BV117411g7ib?from=search&seid=7393394991601580420


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM