Java引用類型原理深度剖析,看完文章,90%的人都收藏了

Java中一共有4種引用類型(其實還有一些其餘的引用類型好比FinalReference):強引用、軟引用、弱引用、虛引用。其中強引用就是咱們常用的Object a = new Object(); 這樣的形式,在Java中並無對應的Reference類。java

本篇文章主要是分析軟引用、弱引用、虛引用的實現,這三種引用類型都是繼承於Reference這個類,主要邏輯也在Reference中。併發

問題

在分析前,先拋幾個問題?jvm

1.網上大多數文章對於軟引用的介紹是:在內存不足的時候纔會被回收,那內存不足是怎麼定義的?什麼才叫內存不足?分佈式

2.網上大多數文章對於虛引用的介紹是:形同虛設,虛引用並不會決定對象的生命週期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?ide

3.虛引用在Jdk中有哪些場景下用到了呢?函數

Reference

咱們先看下Reference.java中的幾個字段高併發

public abstract class Reference<T> {
    //引用的對象
    private T referent;        
	//回收隊列,由使用者在Reference的構造函數中指定
    volatile ReferenceQueue<? super T> queue;
 	//當該引用被加入到queue中的時候,該字段被設置爲queue中的下一個元素,以造成鏈表結構
    volatile Reference next;
    //在GC時,JVM底層會維護一個叫DiscoveredList的鏈表,存放的是Reference對象,discovered字段指向的就是鏈表中的下一個元素,由JVM設置
    transient private Reference<T> discovered;  
	//進行線程同步的鎖對象
    static private class Lock { }
    private static Lock lock = new Lock();
	//等待加入queue的Reference對象,在GC時由JVM設置,會有一個java層的線程(ReferenceHandler)源源不斷的從pending中提取元素加入到queue
    private static Reference<Object> pending = null;
}

一個Reference對象的生命週期以下:oop

 

 

主要分爲Native層和Java層兩個部分。ui

Native層在GC時將須要被回收的Reference對象加入到DiscoveredList中(代碼在referenceProcessor.cppprocess_discovered_references方法),而後將DiscoveredList的元素移動到PendingList中(代碼在referenceProcessor.cppenqueue_discovered_ref_helper方法),PendingList的隊首就是Reference類中的pending對象。this

看看Java層的代碼

private static class ReferenceHandler extends Thread {
     	...
        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
  } 
static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                 	//若是是Cleaner對象,則記錄下來,下面作特殊處理
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    //指向PendingList的下一個對象
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                   //若是pending爲null就先等待,當有對象加入到PendingList中時,jvm會執行notify
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } 
        ...

        // 若是時CLeaner對象,則調用clean方法進行資源回收
        if (c != null) {
            c.clean();
            return true;
        }
		//將Reference加入到ReferenceQueue,開發者能夠經過從ReferenceQueue中poll元素感知到對象被回收的事件。
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
 }

流程比較簡單:就是源源不斷的從PendingList中提取出元素,而後將其加入到ReferenceQueue中去,開發者能夠經過從ReferenceQueue中poll元素感知到對象被回收的事件。

另外須要注意的是,對於Cleaner類型(繼承自虛引用)的對象會有額外的處理:在其指向的對象被回收時,會調用clean方法,該方法主要是用來作對應的資源回收,在堆外內存DirectByteBuffer中就是用Cleaner進行堆外內存的回收,這也是虛引用在java中的典型應用。

看完了Reference的實現,再看看幾個實現類裏,各自有什麼不一樣。

SoftReference

public class SoftReference<T> extends Reference<T> {
    
    static private long clock;
    
    private long timestamp;
   
    public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }
 
    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

    public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }

}

軟引用的實現很簡單,就多了兩個字段:clocktimestampclock是個靜態變量,每次GC時都會將該字段設置成當前時間。timestamp字段則會在每次調用get方法時將其賦值爲clock(若是不相等且對象沒被回收)。

那這兩個字段的做用是什麼呢?這和軟引用在內存不夠的時候才被回收,又有什麼關係呢?

這些還得看JVM的源碼才行,由於決定對象是否須要被回收都是在GC中實現的。

size_t
ReferenceProcessor::process_discovered_reflist(
  DiscoveredList               refs_lists[],
  ReferencePolicy*             policy,
  bool                         clear_referent,
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor)
{
 ...
   //還記得上文提到過的DiscoveredList嗎?refs_lists就是DiscoveredList。
   //對於DiscoveredList的處理分爲幾個階段,SoftReference的處理就在第一階段
 ...
      for (uint i = 0; i < _max_num_q; i++) {
        process_phase1(refs_lists[i], policy,
                       is_alive, keep_alive, complete_gc);
      }
 ...
}

//該階段的主要目的就是當內存足夠時,將對應的SoftReference從refs_list中移除。
void
ReferenceProcessor::process_phase1(DiscoveredList&    refs_list,
                                   ReferencePolicy*   policy,
                                   BoolObjectClosure* is_alive,
                                   OopClosure*        keep_alive,
                                   VoidClosure*       complete_gc) {
  
  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
  // Decide which softly reachable refs should be kept alive.
  while (iter.has_next()) {
    iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */));
    //判斷引用的對象是否存活
    bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive();
    //若是引用的對象已經不存活了,則會去調用對應的ReferencePolicy判斷該對象是不時要被回收
    if (referent_is_dead &&
        !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) {
      if (TraceReferenceGC) {
        gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s"  ") by policy",
                               (void *)iter.obj(), iter.obj()->klass()->internal_name());
      }
      // Remove Reference object from list
      iter.remove();
      // Make the Reference object active again
      iter.make_active();
      // keep the referent around
      iter.make_referent_alive();
      iter.move_to_next();
    } else {
      iter.next();
    }
  }
 ...
}

refs_lists中存放了本次GC發現的某種引用類型(虛引用、軟引用、弱引用等),而process_discovered_reflist方法的做用就是將不須要被回收的對象從refs_lists移除掉,refs_lists最後剩下的元素全是須要被回收的元素,最後會將其第一個元素賦值給上文提到過的Reference.java#pending字段。

ReferencePolicy一共有4種實現:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。其中NeverClearPolicy永遠返回false,表明永遠不回收SoftReference,在JVM中該類沒有被使用,AlwaysClearPolicy則永遠返回true,在referenceProcessor.hpp#setup方法中中能夠設置policy爲AlwaysClearPolicy,至於何時會用到AlwaysClearPolicy,你們有興趣能夠自行研究。

LRUCurrentHeapPolicy和LRUMaxHeapPolicy的should_clear_reference方法則是徹底相同:

bool LRUMaxHeapPolicy::should_clear_reference(oop p,
                                             jlong timestamp_clock) {
  jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
  assert(interval >= 0, "Sanity check");

  // The interval will be zero if the ref was accessed since the last scavenge/gc.
  if(interval <= _max_interval) {
    return false;
  }

  return true;
}

timestamp_clock就是SoftReference的靜態字段clockjava_lang_ref_SoftReference::timestamp(p)對應是字段timestamp。若是上次GC後有調用SoftReference#getinterval值爲0,不然爲若干次GC之間的時間差。

_max_interval則表明了一個臨界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy兩種策略中有差別。

void LRUCurrentHeapPolicy::setup() {
  _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
  assert(_max_interval >= 0,"Sanity check");
}

void LRUMaxHeapPolicy::setup() {
  size_t max_heap = MaxHeapSize;
  max_heap -= Universe::get_heap_used_at_last_gc();
  max_heap /= M;

  _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
  assert(_max_interval >= 0,"Sanity check");
}

其中SoftRefLRUPolicyMSPerMB默認爲1000,前者的計算方法和上次GC後可用堆大小有關,後者計算方法和(堆大小-上次gc時堆使用大小)有關。

看到這裏你就知道SoftReference到底何時被被回收了,它和使用的策略(默認應該是LRUCurrentHeapPolicy),堆可用大小,該SoftReference上一次調用get方法的時間都有關係。

WeakReference

public class WeakReference<T> extends Reference<T> {

    public WeakReference(T referent) {
        super(referent);
    }

    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}

能夠看到WeakReference在Java層只是繼承了Reference,沒有作任何的改動。那referent字段是何時被置爲null的呢?要搞清楚這個問題咱們再看下上文提到過的process_discovered_reflist方法:

size_t
ReferenceProcessor::process_discovered_reflist(
  DiscoveredList               refs_lists[],
  ReferencePolicy*             policy,
  bool                         clear_referent,
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor)
{
 ...

  //Phase 1:將全部不存活可是還不能被回收的軟引用從refs_lists中移除(只有refs_lists爲軟引用的時候,這裏policy纔不爲null)
  if (policy != NULL) {
    if (mt_processing) {
      RefProcPhase1Task phase1(*this, refs_lists, policy, true /*marks_oops_alive*/);
      task_executor->execute(phase1);
    } else {
      for (uint i = 0; i < _max_num_q; i++) {
        process_phase1(refs_lists[i], policy,
                       is_alive, keep_alive, complete_gc);
      }
    }
  } else { // policy == NULL
    assert(refs_lists != _discoveredSoftRefs,
           "Policy must be specified for soft references.");
  }

  // Phase 2:
  // 移除全部指向對象還存活的引用
  if (mt_processing) {
    RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/);
    task_executor->execute(phase2);
  } else {
    for (uint i = 0; i < _max_num_q; i++) {
      process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);
    }
  }

  // Phase 3:
  // 根據clear_referent的值決定是否將不存活對象回收
  if (mt_processing) {
    RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);
    task_executor->execute(phase3);
  } else {
    for (uint i = 0; i < _max_num_q; i++) {
      process_phase3(refs_lists[i], clear_referent,
                     is_alive, keep_alive, complete_gc);
    }
  }

  return total_list_count;
}

void
ReferenceProcessor::process_phase3(DiscoveredList&    refs_list,
                                   bool               clear_referent,
                                   BoolObjectClosure* is_alive,
                                   OopClosure*        keep_alive,
                                   VoidClosure*       complete_gc) {
  ResourceMark rm;
  DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
  while (iter.has_next()) {
    iter.update_discovered();
    iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));
    if (clear_referent) {
      // NULL out referent pointer
      //將Reference的referent字段置爲null,以後會被GC回收
      iter.clear_referent();
    } else {
      // keep the referent around
      //標記引用的對象爲存活,該對象在此次GC將不會被回收
      iter.make_referent_alive();
    }
	...
  }
    ...
}

不論是弱引用仍是其餘引用類型,將字段referent置null的操做都發生在process_phase3中,而具體行爲是由clear_referent的值決定的。而clear_referent的值則和引用類型相關。

ReferenceProcessorStats ReferenceProcessor::process_discovered_references(
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor,
  GCTimer*                     gc_timer) {
  NOT_PRODUCT(verify_ok_to_handle_reflists());
	...
  //process_discovered_reflist方法的第3個字段就是clear_referent
  // Soft references
  size_t soft_count = 0;
  {
    GCTraceTime tt("SoftReference", trace_time, false, gc_timer);
    soft_count =
      process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }

  update_soft_ref_master_clock();

  // Weak references
  size_t weak_count = 0;
  {
    GCTraceTime tt("WeakReference", trace_time, false, gc_timer);
    weak_count =
      process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }

  // Final references
  size_t final_count = 0;
  {
    GCTraceTime tt("FinalReference", trace_time, false, gc_timer);
    final_count =
      process_discovered_reflist(_discoveredFinalRefs, NULL, false,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }

  // Phantom references
  size_t phantom_count = 0;
  {
    GCTraceTime tt("PhantomReference", trace_time, false, gc_timer);
    phantom_count =
      process_discovered_reflist(_discoveredPhantomRefs, NULL, false,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }
	...
}

能夠看到,對於Soft references和Weak references clear_referent字段傳入的都是true,這也符合咱們的預期:對象不可達後,引用字段就會被置爲null,而後對象就會被回收(對於軟引用來講,若是內存足夠的話,在Phase 1,相關的引用就會從refs_list中被移除,到Phase 3時refs_list爲空集合)。

但對於Final references和 Phantom references,clear_referent字段傳入的是false,也就意味着被這兩種引用類型引用的對象,若是沒有其餘額外處理,只要Reference對象還存活,那引用的對象是不會被回收的。Final references和對象是否重寫了finalize方法有關,不在本文分析範圍以內,咱們接下來看看Phantom references。

PhantomReference

public class PhantomReference<T> extends Reference<T> {
 
    public T get() {
        return null;
    }
 
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}

能夠看到虛引用的get方法永遠返回null,咱們看個demo。

public static void demo() throws InterruptedException {
        Object obj = new Object();
        ReferenceQueue<Object> refQueue =new ReferenceQueue<>();
        PhantomReference<Object> phanRef =new PhantomReference<>(obj, refQueue);

        Object objg = phanRef.get();
        //這裏拿到的是null
        System.out.println(objg);
        //讓obj變成垃圾
        obj=null;
        System.gc();
        Thread.sleep(3000);
		//gc後會將phanRef加入到refQueue中
        Reference<? extends Object> phanRefP = refQueue.remove();
     	//這裏輸出true
        System.out.println(phanRefP==phanRef);
    }

從以上代碼中能夠看到,虛引用可以在指向對象不可達時獲得一個'通知'(其實全部繼承References的類都有這個功能),須要注意的是GC完成後,phanRef.referent依然指向以前建立Object,也就是說Object對象一直沒被回收!

而形成這一現象的緣由在上一小節末尾已經說了:對於Final references和 Phantom references,clear_referent字段傳入的時false,也就意味着被這兩種引用類型引用的對象,若是沒有其餘額外處理,在GC中是不會被回收的。

對於虛引用來講,從refQueue.remove();獲得引用對象後,能夠調用clear方法強行解除引用和對象之間的關係,使得對象下次能夠GC時能夠被回收掉。

End

針對文章開頭提出的幾個問題,看完分析,咱們已經能給出回答:

1.咱們常常在網上看到軟引用的介紹是:在內存不足的時候纔會回收,那內存不足是怎麼定義的?爲何才叫內存不足?

軟引用會在內存不足時被回收,內存不足的定義和該引用對象get的時間以及當前堆可用內存大小都有關係,計算公式在上文中也已經給出。

2.網上對於虛引用的介紹是:形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?

嚴格的說,虛引用是會影響對象生命週期的,若是不作任何處理,只要虛引用不被回收,那其引用的對象永遠不會被回收。因此通常來講,從ReferenceQueue中得到PhantomReference對象後,若是PhantomReference對象不會被回收的話(好比被其餘GC ROOT可達的對象引用),須要調用clear方法解除PhantomReference和其引用對象的引用關係。

3.虛引用在Jdk中有哪些場景下用到了呢?

DirectByteBuffer中是用虛引用的子類Cleaner.java來實現堆外內存回收的,後續會寫篇文章來講說堆外內存的裏裏外外。

本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G,須要本身領取。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
相關文章
相關標籤/搜索