歡迎您光臨本站 註冊首頁

淺談java是如何做資源回收補救的

←手機掃碼閱讀     ml5rwbikls @ 2020-06-30 , reply:0

學習java的過程,我們經常談論一個對象的回收,尤其是資源類型,如果沒有顯示的關閉,對象就被回收了,說明出現了資源洩漏。java本身為了防止這種情況,做了一些擔保的方式,確保可以讓未關閉的資源合理回收掉。

finalize回收
 

finalize方式是java對象被回收時觸發的一個方法。java的很多資源對象,都是在finalize中寫了擔保的方法。

    /**     * Ensures that the close method of this file input stream is     * called when there are no more references to it.     *     * @exception IOException if an I/O error occurs.     * @see    java.io.FileInputStream#close()     */    protected void finalize() throws IOException {      if ((fd != null) && (fd != FileDescriptor.in)) {        /* if fd is shared, the references in FileDescriptor         * will ensure that finalizer is only called when         * safe to do so. All references using the fd have         * become unreachable. We can call close()         */        close();      }    }

 

上面是FileInputStream的finalize方法,在方法被調用時,會檢測文件描述符是否存在,如果存在的話就調用close方法。來確保資源的回收。

finalize方法在我們學習java的時候都並不推薦進行重寫,也不推薦寫複雜的邏輯在裡面,主要是因為gc的時候,都會調用這個方法,如果執行的內容太多,就會導致gc被拖長。影響程序的正常運行。而且這裡也只是做一個簡單的擔保。大部分希望的還是編寫代碼的人可以調用close。這樣在做判斷的時候就結束了,而不用真正的調用關閉的代碼。

Cleaner回收
 

在DirectByteBuffer中,使用了一個Cleaner對象進行補救的。

     unsafe.setMemory(base, size, (byte) 0);      if (pa && (base % ps != 0)) {        // Round up to page boundary        address = base + ps - (base & (ps - 1));      } else {        address = base;      }      cleaner = Cleaner.create(this, new Deallocator(base, size, cap));      att = null;

 

申請完資源後,會創建一個Deallocator對象。

   private static class Deallocator      implements Runnable    {        private static Unsafe unsafe = Unsafe.getUnsafe();        private long address;      private long size;      private int capacity;        private Deallocator(long address, long size, int capacity) {        assert (address != 0);        this.address = address;        this.size = size;        this.capacity = capacity;      }        public void run() {        if (address == 0) {          // Paranoia          return;        }        unsafe.freeMemory(address);        address = 0;        Bits.unreserveMemory(size, capacity);      }      }

 

Deallocator的run方法中就進行了資源的釋放。執行的時機就是靠 Cleaner來觸發的。
 

Cleaner是PhantomReference的子類,PhantomReference是Reference的子類。
 

在中有一個ReferenceHandler

   private static class ReferenceHandler extends Thread {

 

他的run方法就是調用cleaner裡的clean方法。這個線程是在靜態塊裡啟動起來的。

      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();      SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {        @Override        public boolean tryHandlePendingReference() {          return tryHandlePending(false);        }      });

 

於此同時,並且給SharedSecrets設置了一個JavaLangRefAccess。
 

調用clean方法的過程在tryHandlePending裡,這裡的參數很重要。

   static boolean tryHandlePending(boolean waitForNotify) {      Referencer;      Cleaner c;      try {        synchronized (lock) {          if (pending != null) {            r = pending;            // 'instanceof' might throw OutOfMemoryError sometimes            // so do this before un-linking 'r' from the 'pending' chain...            c = r instanceof Cleaner ? (Cleaner) r : null;            // unlink 'r' from 'pending' chain            pending = r.discovered;            r.discovered = null;          } else {            // The waiting on the lock may cause an OutOfMemoryError            // because it may try to allocate exception objects.            if (waitForNotify) {              lock.wait();            }            // retry if waited            return waitForNotify;          }        }      } catch (OutOfMemoryError x) {        // Give other threads CPU time so they hopefully drop some live references        // and GC reclaims some space.        // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above        // persistently throws OOME for some time...        Thread.yield();        // retry        return true;      } catch (InterruptedException x) {        // retry        return true;      }

 

waitForNotify是true的時候,在沒有回收對象的時候,會進入阻塞,然後等ooe。外層是個死循環,就會被再次調用到,下次進來的時候就可以出發clean了。
 

ReferenceHandler是管理機制的一種。
 

還有一種就是SharedSecrets調用tryHandlePending(false)。
 

在另外一個類,bits裡

    final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();        // retry while helping enqueue pending Reference objects      // which includes executing pending Cleaner(s) which includes      // Cleaner(s) that free direct buffer memory      while (jlra.tryHandlePendingReference()) {        if (tryReserveMemory(size, cap)) {          return;        }      }

 

在做reserveMemory的時候,會從SharedSecrets來調用tryHandlePending(false)。這裡又變相的進行了一次回收。

小結
 

java回收利用兩種機制。一種是finalize,一種是Cleaner。其中Cleaner一部分依賴oome觸發一次回收,一部分利用reserveMemory中做一次回收。


[ml5rwbikls ] 淺談java是如何做資源回收補救的已經有230次圍觀

http://coctec.com/docs/java/show-post-240439.html