這次把Java的四種引用一次性說清楚

千鋒Python唐小強發表於2020-07-15

前幾天在CodeReview的時候,看到了一個用WeakHashMap的程式碼,進而聊到了WeakReference,再聊到Java四種引用型別。

回想了一下,上次學習Java的強軟弱虛四種引用型別,還是在準備面試的時候。平時用得不多,一下子竟然想不清楚它們的區別,只記得它們的強度依次遞減。

下來又看了一下這方面的文章,今天好好把它們理清楚。

四種引用的區別

其實四種引用的區別在於GC的時候,對它們的處理不同。用一句話來概括,就是:如果一個物件GC Root可達,強引用不會被回收,軟引用在記憶體不足時會被回收,弱引用在這個物件第一次GC會被回收。

如果GC Root不可達,那不論什麼引用,都會被回收

虛引用比較特殊,等於沒有引用,不會影響物件的生命週期,但可以在物件被收集器回收時收到一個系統通知。

下面結合案例分別來講一下四種引用在面對GC時的表現以及它們的常見用途。先設定一下JVM的引數:


-Xms20M 
-Xmx20M 
-Xmn10M 
-verbose
:gc 
-XX
:+PrintGCDetails

強引用

這就是我們平時最常使用的引用。只要GC的時候這個物件GC Root可達,它就不會被回收。如果JVM記憶體不夠了,直接丟擲OOM。比如下面這段程式碼就會丟擲OutOfMemoryError:



public 
static 
void 
main
(String[] args) {

   List<Object> list = new LinkedList<>();
    for ( int i = 0; i < 21; i++) {
        list.add( new byte[ 1024 * 1024]);
   }
}

軟引用

軟引用,當GC的時候,如果GC Root可達,如果記憶體足夠,就不會被回收;如果記憶體不夠用,會被回收。將上面的例子改成軟引用,就不會被OOM:



public 
static 
void 
main(
String[] args) {

   List<Object> list = new LinkedList<>();
    for ( int i = 0; i < 21; i++) {
       SoftReference< byte[]> softReference = new SoftReference<>( new byte[ 1024 * 1024]);
       list. add(softReference);
   }
}

我們把程式改造一下,列印出GC後的前後的差別:



public 
static 
void 
main(
String[] args) {

   List<SoftReference< byte[]>> list = new LinkedList<>();
    for ( int i = 0; i < 21; i++) {
       SoftReference< byte[]> softReference = new SoftReference<>( new byte[ 1024 * 1024]);
       list. add(softReference);
       System. out.println( "gc前:" + softReference. get());
   }
   System.gc();
    for (SoftReference< byte[]> softReference : list) {
       System. out.println( "gc後:" + softReference. get());
   }
}

會發現,列印出的日誌,GC前都是有值的,而GC後,會有一些是null,代表它們已經被回收。

而我們設定的堆最大為20M,如果把迴圈次數改成15,就會發現列印出的日誌,GC後沒有為null的。但透過-verbose:gc -XX:+PrintGCDetails引數能發現,JVM還是進行了幾次GC的,只是由於記憶體還夠用,所以沒有回收。



public 
static 
void 
main(
String[] args) {

   List<SoftReference< byte[]>> list = new LinkedList<>();
    for ( int i = 0; i < 15; i++) {
       SoftReference< byte[]> softReference = new SoftReference<>( new byte[ 1024 * 1024]);
       list. add(softReference);
       System. out.println( "gc前:" + softReference. get());
   }
   System.gc();
    for (SoftReference< byte[]> softReference : list) {
       System. out.println( "gc後:" + softReference. get());
   }
}

所以軟引用的常見用途就呼之欲出了:快取。尤其是那種希望這個快取能夠持續時間長一點的。

弱引用

軟引用,只要這個物件發生GC,就會被回收。

把上面的程式碼改成軟引用,會發現列印出的日誌,GC後全部為null。



public 
static 
void 
main(
String[] args) {

   List<WeakReference< byte[]>> list = new LinkedList<>();
    for ( int i = 0; i < 15; i++) {
       WeakReference< byte[]> weakReference = new WeakReference<>( new byte[ 1024 * 1024]);
       list. add(weakReference);
       System. out.println( "gc前:" + weakReference. get());
   }
   System.gc();
    for (WeakReference< byte[]> weakReference : list) {
       System. out.println( "gc後:" + weakReference. get());
   }
}

所以弱引用也適合用來做快取,不過由於它是隻要發生GC就會被回收,所以存活的時間比軟引用短得多,通常用於做一些非常臨時的快取。

我們知道,WeakHashMap內部是透過弱引用來管理entry的。它的鍵是“弱鍵”,所以在GC時,它對應的鍵值對也會從Map中刪除。

Tomcat中有一個ConcurrentCache,用到了WeakHashMap,結合ConcurrentHashMap,實現了一個執行緒安全的快取,感興趣的同學可以研究一下原始碼,程式碼非常精簡,加上所有註釋,只有短短59行。

ThreadLocal中的靜態內部類ThreadLocalMap裡面的entry是一個WeakReference的繼承類。

使用弱引用,使得ThreadLocalMap知道ThreadLocal物件是否已經失效,一旦該物件失效,也就是成為垃圾,那麼它所操控的Map裡的資料也就沒有用處了,因為外界再也無法訪問,進而決定擦除Map中相關的值物件,Entry物件的引用,來保證Map總是保持儘可能的小。

虛引用

虛引用的設計和上面三種引用有些不同,它並不影響GC,而是為了在物件被GC時,能夠收到一個系統通知。

那它是怎麼被通知的呢?虛引用必須要配合ReferenceQueue,當GC準備回收一個物件,如果發現它還有虛引用,就會在回收之前,把這個虛引用加入到與之關聯的ReferenceQueue中。

那NIO是如何利用虛引用來管理記憶體的呢?

DirectBuffer直接從Java堆之外申請一塊記憶體, 這塊記憶體是不直接受JVM GC管理的, 也就是說在GC演算法中並不會直接操作這塊記憶體. 這塊記憶體的GC是由於DirectBuffer在Java堆中的物件被GC後, 透過一個通知機制, 而將其清理掉的.

DirectBuffer內部有一個Cleaner。這個Cleaner是PhantomReference的子類。當DirectBuffer物件被回收之後, 就會通知到PhantomReference。然後由ReferenceHandler呼叫tryHandlePending()方法進行pending處理. 如果pending不為空, 說明DirectBuffer被回收了, 就可以呼叫Cleaner的clean()進行回收了。

上面這個方法的程式碼在Reference類裡面,有興趣的同學可以去看一下那個方法的原始碼。

總結

以上就是Java中四種引用的區別。一般來說,強引用我們都知道,虛引用很少用到。而軟引用和弱引用的區別在於回收的時機:軟引用GC時,發現記憶體不夠才回收,弱引用只要一GC就會回收。

這次把Java的四種引用一次性說清楚


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69923331/viewspace-2704752/,如需轉載,請註明出處,否則將追究法律責任。

相關文章