<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          讓人蛋疼的JAVA虛引用!

          共 4375字,需瀏覽 9分鐘

           ·

          2021-07-04 17:23

          原創(chuàng):小姐姐味道(微信公眾號ID:xjjdog),歡迎分享,轉(zhuǎn)載請保留出處。

          在Java的世界里,對象的存在層次,也有三六九等,充滿了階層之間的嘲弄。強軟弱虛各種引用,對于熟悉Java的同學一定不會感到陌生,它們隨著等級的降低,越來越?jīng)]存在感。平常使用的對象,大多數(shù)就是強引用的;而軟引用和弱引用,則經(jīng)常在一些堆內(nèi)緩存框架中用到。

          那虛引用呢?傳說中的幽靈引用,是不是就如同它的名字一樣,一無是處呢?

          三種引用

          首先,我們來回顧一下其他三種引用的類型和用途。

          Strong references

          當內(nèi)存空間不足,系統(tǒng)撐不住了,JVM 就會拋出 OutOfMemoryError 錯誤。即使程序會異常終止,這種對象也不會被回收。這種引用屬于最普通最強硬的一種存在,只有在和 GC Roots 斷絕關(guān)系時,才會被消滅掉。

          這種引用,你每天的編碼都在用。例如:new 一個普通的對象。

          Object obj = new Object()

          這種方式可能是有問題的。假如你的系統(tǒng)被大量用戶(User)訪問,你需要記錄這個 User 訪問的時間。可惜的是,User 對象里并沒有這個字段,所以我們決定將這些信息額外開辟一個空間進行存放。

          Soft references

          軟引用用于維護一些可有可無的對象。在內(nèi)存足夠的時候,軟引用對象不會被回收,只有在內(nèi)存不足時,系統(tǒng)則會回收軟引用對象,如果回收了軟引用對象之后仍然沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。

          可以看到,這種特性非常適合用在緩存技術(shù)上。比如網(wǎng)頁緩存、圖片緩存等。

          Guava 的 CacheBuilder,就提供了軟引用和弱引用的設置方式。在這種場景中,軟引用比強引用安全的多。

          軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收,Java 虛擬機就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。

          Weak references

          弱引用對象相比較軟引用,要更加無用一些,它擁有更短的生命周期。

          當 JVM 進行垃圾回收時,無論內(nèi)存是否充足,都會回收被弱引用關(guān)聯(lián)的對象。弱引用擁有更短的生命周期,在 Java 中,用 java.lang.ref.WeakReference 類來表示。

          怪異的虛引用

          以上幾個引用級別都很好理解,但是虛引用是個例外。虛引用可以使用下面的代碼定義:

          Object  object = new Object();
          ReferenceQueue queue = new ReferenceQueue();
          // 虛引用,必須與一個引用隊列關(guān)聯(lián)
          PhantomReference pr = new PhantomReference(object, queue);

          但是當你想取出其中的值時(get),得到的卻總是null。

          //JDK源碼   
          /**
               * Returns this reference object's referent.  Because the referent of a
               * phantom reference is always inaccessible, this method always returns
               * {@code null}.
               *
               * @return {@code null}
               */

              public T get() {
                  return null;
              }

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

          當垃圾回收器準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象之前,把這個虛引用加入到與之關(guān)聯(lián)的引用隊列中。

          程序如果發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動。

          桃花源深處

          在hotspot的jvm中,有一個叫做cleaner的類,其實就是虛引用典型的應用。可以看到Cleaner是直接簡單粗暴的繼承了PhantomReference,所以它本質(zhì)上就是一個虛引用,只不過多了一些便捷的操作。


          那么這個類是在什么地方用到的呢?大家手上應該都有jdk的源代碼,追蹤一下,發(fā)現(xiàn)最后竟然是DirectByteBuffer用到了它。


          直接內(nèi)存,一直是一個看起來非常高大上的名詞,基本上和高性能掛鉤,但也容易產(chǎn)生內(nèi)存泄漏。由于直接內(nèi)存,是屬于堆外內(nèi)存的,所以垃圾回收的時候,就不能靠JVM的那一套垃圾回收算法進行清理。

          事實上,由于DirectByteBuffer可能會被使用較長時間,熬過了年輕代的各種回收,就會進入老年代。這時候就比較麻煩了,這些引用對象,要在下一輪Old GC或者Full GC才能觸發(fā),如果你的老年代空間較大,觸發(fā)回收的操作就需要等很久很久。問題是,在這段時間內(nèi),雖然這些堆外內(nèi)存不再使用了,但它仍然占用著較大的物理空間,最后造成嚴重的浪費甚至崩潰。

          對堆外內(nèi)存不是很熟悉的同學,可以看我以前的一張圖。或者直接看這篇文章。通過-XX:MaxDirectMemorySize可以限制直接內(nèi)存的使用上限。

          《一圖解千愁,jvm內(nèi)存從來沒有這么簡單過!》


          那么這些堆外內(nèi)存是如何進行回收的呢?這就是Cleaner的作用。Cleaner通過next和prev構(gòu)造了一個典型的鏈表,但它本身是沒有任何邏輯的,因為它的清理邏輯都在thunk方法中。

          cleaner = Cleaner.create(thisnew Deallocator(base, size, cap));

          public void clean() {
                  if (remove(this)) {
                      try {
                          this.thunk.run();

          也就是Deallocator = De allocator。其中,傳入的base,就是靠unsafe類申請的堆外內(nèi)存地址引用(僅僅是個地址),有了引用和容量,其實我們就能夠在回收的時候定位到真正的堆外內(nèi)存塊。就像Deallocator做的一樣。

          public void run() {
            if (address == 0) {
              // Paranoia
              return;
            }
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
          }

          機制上沒什么問題,關(guān)鍵要看它們是怎么聯(lián)系起來的。這種問題,當然是要靠其他線程完成,這里就是ReferenceHandler。很熟悉的名字,你每次使用jstack命令導出堆棧,都會看到它。

          Thread handler = new ReferenceHandler(tg, "Reference Handler");
          /* If there were a special system-only priority greater than
          * MAX_PRIORITY, it would be used here
          */

          handler.setPriority(Thread.MAX_PRIORITY);
          handler.setDaemon(true);
          handler.start();

          真正去工作的方法,是tryHandlePending,然后在這里,調(diào)用Cleaner的clean方法,進而調(diào)用真正的清理方法,釋放堆外內(nèi)存。它會從虛引用注冊的隊列里,取出新的對象,然后判斷是不是Cleaner類型,如果是,就進行一次清理。

          End

          這就是虛引用。它存在的唯一目的,就是在回收的時候,能夠被感知到,以便進行更深層次的清理。在commons-io包的FileCleaningTracker類中,同樣有繼承了虛引用的Tracker類,用來跟蹤后續(xù)文件的一些清理工作。這個沒存在感的小小虛引用,默默的承擔起最后一道防線,是系統(tǒng)正常運行的有效保證。

          不要小看它,它無處不在。因為你的每一個JVM進程,都跑著一個叫做Reference Handler的線程呢。

          瀏覽 147
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草大香蕉人人噜视频免费 | 岛国成人电影在线网站 | 国产一级A片免费播放 | 中日欧美中文字幕第一页 | 国产人人摸 |