天天看點

Java--引用類型--強引用、軟引用、弱引用、虛引用--差別/使用/執行個體

簡介

說明

        本文用示例介紹Java中的引用類型的差別及使用。

        從JDK 1.2版本開始,對象的引用被劃分為4種級别,由高到低依次為:強引用、軟引用、弱引用和虛引用。

        本内容也是Java後端面試常見的問題。

為什麼分為四種引用類型?

Java設計這四種引用的主要目的有兩個:

  1. 可以讓程式員通過代碼的方式來決定某個對象的生命周期
  2. 有利于垃圾回收

引用類型簡介

  • 強引用
  • 強引用是使用最普遍的引用,我們寫的代碼,99.9999%都是強引用
  • 隻要某個對象有強引用與之關聯,這個對象永遠不會被回收,即使記憶體不足,JVM甯願抛出OOM,也不會去回收。
  • 軟引用
  • 隻有在記憶體不足時,JVM才會回收該對象。
  • 當記憶體不足時,會觸發JVM的GC,如果GC後,記憶體還是不足,就會把軟引用包裹的對象給幹掉。
  • 弱引用
  • 不管記憶體是否足夠,隻要發生GC,弱引用就會被回收。
  • 虛引用
  • 無法通過虛引用來擷取對一個對象的真實引用。
  • 虛引用必須與ReferenceQueue一起使用。當GC準備回收一個對象時,如果發現它還有虛引用,就會在回收之前,把這個虛引用加入到與之關聯的ReferenceQueue中。

強引用

簡介

        強引用是使用最普遍的引用,我們寫的代碼,99.9999%都是強引用。

        隻要某個對象有強引用與之關聯,這個對象永遠不會被回收,即使記憶體不足,JVM甯願抛出OOM,也不會去回收。

        強引用寫法:Object o = new Object();

        讓強引用的對象被回收的寫法:o = null;

代碼執行個體

用代碼展示回收強引用的對象。

finalize方法在垃圾回收的時候會被調用。

package com.example.a;

class User{
    @Override
    protected void finalize() {
        System.out.println("User 被回收了");
    }
}

public class Demo {
    public static void main(String[] args) {
        User user = new User();
        user = null;
        System.gc();
    }
}      

結果

User 被回收了      

總結

  1. 可以看到資源被回收了。
  2. 在實際開發中,不要重寫finalize方法
  3. 在實際開發中,若看到有對象被手動指派為null,很大可能就是為了“特意提醒”JVM這塊資源可以進行垃圾回收了。

軟引用

簡介

說明

        隻有在記憶體不足時,JVM才會回收該對象。

        當記憶體不足時,會觸發JVM的GC,如果GC後,記憶體還是不足,就會把軟引用包裹的對象給幹掉。

應用場景:

        軟引用主要用于緩存:當記憶體足夠時,可以正常的拿到緩存,當記憶體不夠時,就會先幹掉緩存,不至于馬上抛出OOM。

        比如:浏覽器的後退按鈕。按後退時,這個後退時顯示的網頁内容是重新進行請求還是從緩存中取出呢?這就要看具體的實作政策了。

  1. 如果一個網頁在浏覽結束時就進行内容的回收,則按後退檢視前面浏覽過的頁面時,需要重新建構;
  2. 如果将浏覽過的網頁存儲到記憶體中會造成記憶體的大量浪費,甚至會造成記憶體溢出。

這時候就可以使用軟引用,很好的解決了實際的問題:

// 擷取浏覽器對象進行浏覽
    Browser browser = new Browser();
    // 從背景程式加載浏覽頁面
    BrowserPage page = browser.getPage();
    // 将浏覽完畢的頁面置為軟引用
    SoftReference softReference = new SoftReference(page);

    // 回退或者再次浏覽此頁面時
    if(softReference.get() != null) {
        // 記憶體充足,還沒有被回收器回收,直接擷取緩存
        page = softReference.get();
    } else {
        // 記憶體不足,軟引用的對象已經回收
        page = browser.getPage();
        // 重新建構軟引用
        softReference = new SoftReference(page);
    }      

代碼執行個體

代碼寫法

建立一個軟引用:

SoftReference<User> softReference = new SoftReference<User>(new User());      

從軟引用對象獲得包裹的對象:

User User = softReference.get();
System.out.println(User);      

執行個體:用代碼展示回收軟引用的對象

定義一個軟引用對象,裡面包裹了byte[],byte[]占用了10M,然後又建立了10Mbyte[]。

package com.example.a;

import java.lang.ref.SoftReference;

public class Demo {
    public static void main(String[] args) {
        SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
        System.out.println(softReference.get());
        System.gc();
        System.out.println(softReference.get());

        byte[] bytes = new byte[1024 * 1024 * 10];
        System.out.println(softReference.get());
    }
}      

執行結果

[B@1b6d3586
[B@1b6d3586
[B@1b6d3586      

現在我們手動制造記憶體不足:設定最大堆大小為15M:-Xmx15m

法1:先編譯程式,再java -Xmx15m xxx

法2:Idea指定參數

Java--引用類型--強引用、軟引用、弱引用、虛引用--差別/使用/執行個體
Java--引用類型--強引用、軟引用、弱引用、虛引用--差別/使用/執行個體

執行結果

[B@1b6d3586
[B@1b6d3586
null      

 結論

        手動GC後,軟引用對象包裹的byte[]還活的好好的,但是當我們建立了一個10M的byte[]後,最大堆記憶體不夠了,是以JVM把軟引用對象包裹的byte[]給幹掉了。

弱引用

簡介

說明

不管記憶體是否足夠,隻要發生GC,弱引用就會被回收。

使用場景

        若引用主要用于緩存:當沒有發生GC時,可以正常的拿到緩存,當發生GC時,就會先幹掉緩存,不至于馬上抛出OOM。

        JDK裡對弱引用的使用:​​ThreadLocal​​、WeakHashMap。

        如果一個對象是偶爾(很少)的使用,并且希望在使用時随時就能擷取到,但又不想影響此對象的垃圾收集,就可以使用弱引用。

代碼執行個體

代碼寫法

弱引用的使用和軟引用類似,隻是關鍵字變成了WeakReference

建立一個軟引用:

WeakReference<User> weakReference = new WeakReference<User>(new User());      

從軟引用對象獲得包裹的對象:

User User = weakReference.get();
System.out.println(User);      

執行個體:用代碼展示回收弱引用的對象

package com.example.a;

import java.lang.ref.WeakReference;

public class Demo {
    public static void main(String[] args) {
        WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
        System.out.println(weakReference.get());
        System.gc();
        System.out.println(weakReference.get());
    }
}      

 結果

[B@1b6d3586
null      

結論

不管記憶體是否足夠,隻要發生GC,弱引用就會被回收。

虛引用

簡介

說明

        無法通過虛引用來擷取對一個對象的真實引用。

        虛引用必須與ReferenceQueue一起使用。當GC準備回收一個對象時,如果發現它還有虛引用,就會在回收之前,把這個虛引用加入到與之關聯的ReferenceQueue中。

使用場景

        虛引用主要用來跟蹤對象被垃圾回收器回收的活動。

        程式可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否将要進行垃圾回收。如果程式發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的記憶體被回收之前采取必要的行動。

        NIO運用了虛引用管理堆外記憶體。

代碼執行個體

簡單執行個體

package com.example.a;

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

public class Demo {
    public static void main(String[] args) {
        ReferenceQueue queue = new ReferenceQueue();
        PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
        System.out.println(reference.get());
    }
}      

結果

null      

看源碼:竟然直接傳回了null。

Java--引用類型--強引用、軟引用、弱引用、虛引用--差別/使用/執行個體

這就是虛引用特點之一:無法通過虛引用來擷取對一個對象的真實引用。 

執行個體:用代碼展示回收弱引用的對象

package com.example.a;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

class User {
    @Override
    protected void finalize() {
        System.out.println("User 被回收了");
    }
}

public class Demo {
    public static void main(String[] args) {
        ReferenceQueue queue = new ReferenceQueue();
        List<byte[]> bytes = new ArrayList<>();
        PhantomReference<User> phantomReference = new PhantomReference<User>(new User(), queue);
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                bytes.add(new byte[1024 * 1024]);
            }
        }).start();

        new Thread(() -> {
            while (true) {
                Reference poll = queue.poll();
                if (poll != null) {
                    System.out.println("虛引用被回收了:" + poll);
                }
            }
        }).start();
        Scanner scanner = new Scanner(System.in);
        scanner.hasNext();
    }
}      

我們手動制造記憶體不足:設定最大堆大小為15M:-Xmx15m

結果:

User 被回收了
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
  at com.example.a.Demo.lambda$main$0(Demo.java:24)
  at com.example.a.Demo$$Lambda$1/2003749087.run(Unknown Source)
  at java.lang.Thread.run(Thread.java:748)
虛引用被回收了:java.lang.ref.PhantomReference@2c4772fc      

其他網址

繼續閱讀