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

千鋒Python唐小強發表於2020-06-29

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

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

finalize回收

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

    
/**

    * Ensures that the <code>close</code> 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) {

       Reference<Object> r;
       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中做一次回收


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

相關文章