JVM-Reference

  1. 感知GC满力。
    怎么感知:
    * 通過get來判斷已經被GC(PhantomReference 在任何時候get都是null冒窍,F(xiàn)inaliner在觸發(fā)queue入隊的時候俯树,get還不是null)闰歪。
    * 都能搭配Queue榛泛,通過Queue通知蝌蹂。
    能用來干什么:
    * 內存泄漏檢測(netty的內存泄漏檢測、ThreadLocal的內存泄漏探測)
    * 資源回收(ThreadLocal探測到內存泄漏之后用過threadLoca.get和set還能清除泄漏的對象)
    • 其他曹锨?
  2. 影響GC孤个。
    SoftReference(OOM之前+其他策略) 會造成都進入old區(qū),頻繁full gc
    WeakReference(GC立刻)
    FinalReference(finalize方法) finalize可能執(zhí)行的時間很長沛简,會造成都進入old區(qū)齐鲤,頻繁full gc

參考自:http://www.androidstar.cn/java%E5%AD%A6%E4%B9%A0%E6%95%99%E7%A8%8B%E4%B9%8Breference%E8%AF%A6%E8%A7%A3/
JAVA引用體系中我們最熟悉的就是強引用類型,如 A a= new A();這是我們經常說的強引用StrongReference椒楣,jvm gc時會檢測對象是否存在強引用给郊,如果存在由根對象對其有傳遞的強引用,則不會對其進行回收捧灰,即使內存不足拋出OutOfMemoryError淆九。

除了強引用外,Java還引入了SoftReference,WeakReference炭庙,PhantomReference跪另,F(xiàn)inalReference ,這些類放在java.lang.ref包下煤搜,類的繼承體系如下圖免绿。Java額外引入這個四種類型引用主要目的是在jvm 在gc時,按照引用類型的不同擦盾,在回收時采用不同的邏輯嘲驾。可以把這些引用看作是對對象的一層包裹迹卢,jvm根據(jù)外層不同的包裹辽故,對其包裹的對象采用不同的回收策略,或特殊邏輯處理腐碱。 這幾種類型的引用主要在jvm內存緩存誊垢、資源釋放、對象可達性事件處理等場景會用到症见。

java_Reference

名稱說明下:Reference指代引用對象本身喂走,Referent指代被引用對象,下文介紹會以Reference谋作,Referent形式出現(xiàn)芋肠。 下面我們先介紹一下Java對象可達性判斷邏輯和ReferenceQueue,然后依次對這四種引用使用和作用進行說明遵蚜。

對象可達性判斷

jvm gc時帖池,判斷一個對象是否存在引用時,都是從根結合引用(Root Set of References)開始去標識,往往到達一個對象的引用路徑會存在多條吭净,如下圖睡汹。


java_Reference

那么 垃圾回收時會依據(jù)兩個原則來判斷對象的可達性:

單一路徑中,以最弱的引用為準
多路徑中寂殉,以最強的引用為準
例如Obj4的引用囚巴,存在3個路徑:1->6、2->5不撑、3->4, 那么從根對象到Obj4最強的引用是2->5文兢,因為它們都是強引用晤斩。如果僅僅存在一個路徑對Obj4有引用時焕檬,比如現(xiàn)在只剩1->6,那么根對象到Obj4的引用就是以最弱的為準,就是SoftReference引用,Obj4就是softly-reachable對象澳泵。

ReferenceQueue VS Reference

Reference作為SoftReference实愚,WeakReference,PhantomReference,F(xiàn)inalReference這幾個引用類型的父類腊敲。主要有兩個字段referent击喂、queue,一個是指所引用的對象碰辅,一個是與之對應的ReferenceQueue懂昂。Reference類有個構造函數(shù) Reference(T referent, ReferenceQueue<? super T> queue),可以通過該構造函數(shù)傳入與Reference相伴的ReferenceQueue没宾。

ReferenceQueue本身提供隊列的功能凌彬,有入隊(enqueue)和出隊(poll,remove,其中remove阻塞等待提取隊列元素)。ReferenceQueue對象本身保存了一個Reference類型的head節(jié)點循衰,Reference封裝了next字段铲敛,這樣就是可以組成一個單向鏈表。同時ReferenceQueue提供了兩個靜態(tài)字段NULL会钝,ENQUEUED

static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();

這兩個字段的主要功能:NULL是當我們構造Reference實例時queue傳入null時伐蒋,會默認使用NULL,這樣在enqueue時判斷queue是否為NULL,如果為NULL直接返回迁酸,入隊失敗先鱼。ENQUEUED的作用是防止重復入隊,reference后會把其queue字段賦值為ENQUEUED,當再次入隊時會直接返回失敗奸鬓。

    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        synchronized (lock) {
            // Check that since getting the lock this reference hasn’t already been
            // enqueued (and even then removed)
            ReferenceQueue<?> queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            r.queue = ENQUEUED;
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }

Reference與ReferenceQueue之間是如何工作的呢型型?

    /* List of References waiting to be enqueued.  The collector adds
     * References to this list, while the Reference-handler thread removes
     * them.  This list is protected by the above lock object. The
     * list uses the discovered field to link its elements.
     */
    private static Reference<Object> pending = null;

Reference里有個靜態(tài)字段pending,當一個Reference的referent被回收時全蝶,垃圾回收器會把reference添加到pending這個鏈表里闹蒜。

 /**
     * Try handle pending {@link Reference} if there is one.<p>
     * Return {@code true} as a hint that there might be another
     * {@link Reference} pending or {@code false} when there are no more pending
     * {@link Reference}s at the moment and the program can do some other
     * useful work instead of looping.
     *
     * @param waitForNotify if {@code true} and there was no pending
     *                      {@link Reference}, wait until notified from VM
     *                      or interrupted; if {@code false}, return immediately
     *                      when there is no pending {@link Reference}.
     * @return {@code true} if there was a {@link Reference} pending and it
     *         was processed, or we waited for notification and either got it
     *         or thread was interrupted before being notified;
     *         {@code false} otherwise.
     */
    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;
        }

        // Fast path for cleaners
        if (c != null) {
            c.clean();
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        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();

        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }

同時還通過靜態(tài)代碼塊啟動了Reference-handler(線程優(yōu)先級最高),該線程循環(huán)執(zhí)行tryHandlePending方法抑淫,參見上方代碼绷落。Reference-handler thread不斷的讀取pending中的reference,把它加入到對應的ReferenceQueue中始苇。我們可以通過下面代碼塊來進行把SoftReference砌烁,WeakReference,PhantomReference與ReferenceQueue聯(lián)合使用來驗證這個機制催式。為了確保SoftReference在每次gc后函喉,其引用的referent都被回收,我們需要加入-XX:SoftRefLRUPolicyMSPerMB=0參數(shù)荣月,這個原理下文中會在講管呵。

/**
 * 為了確保System.gc()后,SoftReference引用的referent被回收需要加入下面的參數(shù)
 * -XX:SoftRefLRUPolicyMSPerMB=0
 */
public class ReferenceTest {
    private static List<Reference> roots = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        ReferenceQueue rq = new ReferenceQueue();

        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while (true) {
                    try {
                        Reference r = rq.remove();
                        System.out.println(“reference:”+r);
                        //為null說明referent被回收
                        System.out.println( “get:”+r.get());
                        i++;
                        System.out.println( “queue remove num:”+i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        for(int i=0;i<100000;i++) {
            byte[] a = new byte[1024*1024];
            // 分別驗證SoftReference,WeakReference,PhantomReference
            Reference r = new SoftReference(a, rq);
            //Reference r = new WeakReference(a, rq);
            //Reference r = new PhantomReference(a, rq);
            roots.add(r);
            System.gc();

            System.out.println(“produce”+i);
            TimeUnit.MILLISECONDS.sleep(100);
        }
    }
}

通過jstack命令可以看到對應的Reference Handler thread

“Reference Handler” #2 daemon prio=10 os_prio=31 tid=0x00007f8fb2836800 nid=0x2e03 in Object.wait() [0x000070000082b000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        – waiting on <0x0000000740008878> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        – locked <0x0000000740008878> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

因此可以看出,當reference與referenQueue聯(lián)合使用的主要作用就是當reference指向的referent回收時(或者要被回收 如下文要講的Finalizer)哺窄,提供一種通知機制捐下,通過queue取到這些reference账锹,來做額外的處理工作。當然坷襟,如果我們不需要這種通知機制奸柬,我們就不用傳入額外的queue,默認使用NULL queue就會入隊失敗。

SoftReference

根據(jù)上面我們講的對象可達性原理婴程,我們把一個對象存在根對象對其有直接或間接的SoftReference廓奕,并沒有其他強引用路徑,我們把該對象成為softly-reachable對象档叔。JVM保證在拋出OutOfMemoryError前會回收這些softly-reachable對象懂从。JVM會根據(jù)當前內存的情況來決定是否回收softly-reachable對象,但只要referent有強引用存在蹲蒲,該referent就一定不會被清理番甩,因此SoftReference適合用來實現(xiàn)memory-sensitive caches。軟引用的回收策略在不同的JVM實現(xiàn)會略有不同届搁,javadoc中說明:

Virtual machine implementations are, however, encouraged to bias against clearing recently-created or recently-used soft references.

也就是說JVM不僅僅只會考慮當前內存情況缘薛,還會考慮軟引用所指向的referent最近使用情況和創(chuàng)建時間來綜合決定是否回收該referent。

Hotspot在gc時會根據(jù)兩個標準來回收:

  1. SoftReference引用實例的timestamp(每次調用softReference.get()會自動更新該字段
  2. 把最近一次垃圾回收時間賦值給timestamp,見源碼)和當前JVM heap的內存剩余(free_heap)情況

計算的規(guī)則是:
free_heap 表示當前堆剩余的內存卡睦,單位是MB
interval 表示最近一次GC’s clock 和 當前我們要判斷的softReference的timestamp 差值
ms_per_mb is a constant number of milliseconds to keep around a SoftReference for each free megabyte in the heap(可以通過-XX:SoftRefLRUPolicyMSPerMB來設定)
那么判斷依據(jù)就是: interval <= freeheap * ms_per_mb,如果為true,則保留宴胧,false則進行對象清除。 SoftReferences will always be kept for at least one GC after their last access表锻。 因為 只要調用一次恕齐,那么clock和timestamp的值就會一樣,clock-timestamp則為0瞬逊,一定小于等于free_heap * ms_per_mb显歧。 OpenJDK的大概referencePolicy.cpp代碼是:

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”);
}

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;
}

可見,SoftReference在一定程度上會影響JVM GC的确镊,例如softly-reachable對應的referent多次垃圾回收仍然不滿足釋放條件士骤,那么它會停留在heap old區(qū),占據(jù)很大部分空間蕾域,在JVM沒有拋出OutOfMemoryError前拷肌,它有可能會導致頻繁的Full GC。 github有個基于android源碼LruCache改造成的 LruSoftCache旨巷,自行驗證巨缘。

WeakReference

當一個對象被WeakReference引用時,處于weakly-reachable狀態(tài)時采呐,只要發(fā)生GC時若锁,就會被清除,同時會把WeakReference注冊到引用隊列中(如果存在的話)懈万。 WeakReference不阻礙或影響它們對應的referent被終結(finalized)和回收(reclaimed)拴清,因此,WeakReference經常被用作實現(xiàn)規(guī)范映射(canonicalizing mappings)会通。相比SoftReference來說口予,WeakReference對JVM GC幾乎是沒有影響的。

WeakReference應用之WeakHashMap

下面我們舉個WeakReference應用場景涕侈,JDK自帶的WeakHashMap沪停,我們用下面的代碼來測試查看WeakHashMap在gc后的entry的情況,加入-verbose:gc運行裳涛。

/**
 * 加入下面參數(shù)木张,觀察gc情況
 * -verbose:gc
 */
public class WeakHashMapTest {
    private static Map<String,byte[]> caches=new WeakHashMap<>();

    public static void main(String[]args) throws InterruptedException {
        for (int i=0;i<100000;i++){
            caches.put(i+””,new byte[1024*1024*10]);
            System.out.println(“put num: ” + i + ” but caches size:” + caches.size());
        }
    }
}

運行代碼我們可以看到,雖然我們不斷的往caches中put元素端三,但是caches size會伴隨每次gc又從0開始了舷礼。

put num: 0 but caches size:1
put num: 1 but caches size:2
[GC (Allocation Failure)  23142K->20936K(125952K), 0.0199681 secs]
put num: 2 but caches size:1
put num: 3 but caches size:2
put num: 4 but caches size:3
[GC (Allocation Failure)  52293K->51672K(159232K), 0.0157178 secs]
put num: 5 but caches size:1
put num: 6 but caches size:2
put num: 7 but caches size:3
put num: 8 but caches size:4
put num: 9 but caches size:5
put num: 10 but caches size:6
[GC (Allocation Failure)  115728K->113064K(191488K), 0.0295324 secs]
[Full GC (Ergonomics)  113064K->61788K(237568K), 0.0172315 secs]
put num: 11 but caches size:1
put num: 12 but caches size:2
put num: 13 but caches size:3
put num: 14 but caches size:4
put num: 15 but caches size:5
put num: 16 but caches size:6
[GC (Allocation Failure)  124511K->123356K(291840K), 0.0174441 secs]
[Full GC (Ergonomics)  123356K->61788K(315392K), 0.0133423 secs]
put num: 17 but caches size:1
put num: 18 but caches size:2
put num: 19 but caches size:3

WeakHashMap實現(xiàn)原理很簡單,它除了實現(xiàn)標準的Map接口郊闯,里面的機制也和HashMap的實現(xiàn)類似妻献。從它entry子類中可以看出,它的key是用WeakReference包裹住的团赁。當這個key對象本身不再被使用時育拨,伴隨著GC的發(fā)生,會自動把該key對應的entry都在Map中清除掉欢摄。它為啥能夠自動清除呢熬丧?這就是利用上面我們講的ReferenceQueue VS Reference的原理。WeakHashMap里聲明了一個queue怀挠,Entry繼承WeakReference,構造函數(shù)中用key和queue關聯(lián)構造一個weakReference,當key不再被使用gc后會自動把把key注冊到queue中:

    /**
     * Reference queue for cleared WeakEntries
     */
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

   /**
     * The entries in this hash table extend WeakReference, using its main ref
     * field as the key.
     */
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
       //代碼省落
    }
}

WeakHashMap關鍵的清理entry代碼:

  /**
     * Expunges stale entries from the table.
     */
    private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings(“unchecked”)
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);

                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                while (p != null) {
                    Entry<K,V> next = p.next;
                    if (p == e) {
                        if (prev == e)
                            table[i] = next;
                        else
                            prev.next = next;
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        e.value = null; // Help GC
                        size–;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

這段代碼會在resize,getTable,size里執(zhí)行析蝴,清除失效的entry。
要想WeakHashMap能夠釋放掉key被GC的value的對象绿淋,盡可能的多調用下put/size/get等操作嫌变,因為這些方法會調用expungeStaleEntries方法,expungeStaleEntries方法是關鍵躬它,而如果不操作WeakHashMap腾啥,以企圖WeakHashMap“自動”釋放內存是不可取的,這里的“自動”是指譬如map.put(obj,new byte[10M])冯吓;之后obj=null了倘待,之后再也沒掉用過map的任何方法,那么new出來的10M空間是不會釋放的组贺。

PhantomReference

PhantomReference 不同于WeakReference凸舵、SoftReference,它存在的意義不是為了獲取referent,因為你也永遠獲取不到失尖,因為它的get如下

 public T get() {
        return null;
 }

PhantomReference主要作為其指向的referent被回收時的一種通知機制,它就是利用上文講到的ReferenceQueue實現(xiàn)的啊奄。當referent被gc回收時渐苏,JVM自動把PhantomReference對象(reference)本身加入到ReferenceQueue中,像發(fā)出信號通知一樣菇夸,表明該reference指向的referent被回收院究。然后可以通過去queue中取到reference侠坎,此時說明其指向的referent已經被回收死遭,可以通過這個通知機制來做額外的清場工作(比如資源釋放澄峰,泄漏檢測等)。 因此有些情況可以用PhantomReference 代替finalize()择诈,做資源釋放更明智械蹋。

下面舉個例子,用PhantomReference來自動關閉文件流羞芍。

public class ResourcePhantomReference<T> extends PhantomReference<T> {

    private List<Closeable> closeables;

    public ResourcePhantomReference(T referent, ReferenceQueue<? super T> q, List<Closeable> resource) {
        super(referent, q);
        closeables = resource;
    }

    public void cleanUp() {
        if (closeables == null || closeables.size() == 0)
            return;
        for (Closeable closeable : closeables) {
            try {
                closeable.close();
                System.out.println(“clean up:”+closeable);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ResourceCloseDeamon extends Thread {

    private static ReferenceQueue QUEUE = new ReferenceQueue();

    //保持對reference的引用,防止reference本身被回收
    private static List<Reference> references=new ArrayList<>();
    @Override
    public void run() {
        this.setName(“ResourceCloseDeamon”);
        while (true) {
            try {
                ResourcePhantomReference reference = (ResourcePhantomReference) QUEUE.remove();
                reference.cleanUp();
                references.remove(reference);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void register(Object referent, List<Closeable> closeables) {
        references.add(new ResourcePhantomReference(referent,QUEUE,closeables));
    }
}
public class FileOperation {
    private FileOutputStream outputStream;
    private FileInputStream inputStream;

    public FileOperation(FileInputStream inputStream, FileOutputStream outputStream) {
        this.outputStream = outputStream;
        this.inputStream = inputStream;
    }

    public void operate() {
        try {
            inputStream.getChannel().transferTo(0, inputStream.getChannel().size(), outputStream.getChannel());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

測試代碼:

public class PhantomTest {
    public static void main(String[] args) throws Exception {
        //打開回收
        ResourceCloseDeamon deamon = new ResourceCloseDeamon();
        deamon.setDaemon(true);
        deamon.start();

        // touch a.txt b.txt
        // echo “hello” > a.txt

        //保留對象,防止gc把stream回收掉,其不到演示效果
        List<Closeable> all=new ArrayList<>();
        FileInputStream inputStream;
        FileOutputStream outputStream;

        for (int i = 0; i < 100000; i++) {
            inputStream = new FileInputStream(“/Users/robin/a.txt”);
            outputStream = new FileOutputStream(“/Users/robin/b.txt”);
            FileOperation operation = new FileOperation(inputStream, outputStream);
            operation.operate();
            TimeUnit.MILLISECONDS.sleep(100);

            List<Closeable>closeables=new ArrayList<>();
            closeables.add(inputStream);
            closeables.add(outputStream);
            all.addAll(closeables);
            ResourceCloseDeamon.register(operation,closeables);
            //用下面命令查看文件句柄,如果把上面register注釋掉,就會發(fā)現(xiàn)句柄數(shù)量不斷上升
            //jps | grep PhantomTest | awk ‘{print $1}’ |head -1 | xargs  lsof -p  | grep /User/robin
            System.gc();

        }
    }
}

運行上面的代碼哗戈,通過

jps | grep PhantomTest | awk ‘{print $1}’ |head -1 | xargs lsof -p | grep /User/robin | wc -l 

可以看到句柄沒有上升,而去掉ResourceCloseDeamon.register(operation,closeables);時荷科,句柄就不會被釋放谱醇。

PhantomReference使用時一定要傳一個referenceQueue,當然也可以傳null,但是這樣就毫無意義了。因為PhantomReference的get結果為null,如果在把queue設為null,那么在其指向的referent被回收時步做,reference本身將永遠不會可能被加入隊列中副渴。

FinalReference

FinalReference 引用類型主要是為虛擬機提供的,提供對象被gc前需要執(zhí)行finalize方法的機制全度。
這部分內容也可以參見http://www.reibang.com/p/948042092541
FinalReference 很簡單就是extend Reference類煮剧,沒有做其他邏輯,只是把訪問權限改為package,因此我們是無法直接使用的将鸵。Finalizer類是我們要講的重點勉盅,它繼承了FinalReference,并且是final 類型的顶掉。Finalize實現(xiàn)很簡單草娜,也是利用上面我們講的ReferenceQueue VS Reference機制。
雖然被放到了ReferenceQueue痒筒,但是.get()還是能獲得referent宰闰。
FinalizerThread
Finalizer靜態(tài)代碼塊里啟動了一個deamon線程,我們通過jstack命令查看線程時簿透,總會看到一個Finalizer線程移袍,就是這個原因:

 static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY – 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }

FinalizerThread run方法是不斷的從queue中去取Finalizer類型的reference,然后執(zhí)行runFinalizer釋放方法老充。

     public void run() {
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            while (!VM.isBooted()) {
                // delay until VM completes initialization
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
       }

runFinalizer方法體如下,該方法包含是否需要再調用finalize方法葡盗、從undifined隊列移除Reference和執(zhí)行finalize操作》茸牵可以看出如果finalize方法中拋出異常會被直接吃掉:

    private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();//從undifined隊列移除Reference
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);

                /* Clear stack slot containing this variable, to decrease
                   the chances of false retention with a conservative GC */
                finalizee = null;
            }
        } catch (Throwable x) { }
        super.clear();
    }

介紹完上面的處理機制觅够,那么剩下的就是入queue的事情胶背,就是哪些類對象需要入隊,何時入隊喘先,下面我們一一介紹钳吟。

哪些類對象是Finalizer reference類型的referent呢

只要類覆寫了Object 上的finalize方法,方法體非空苹祟。那么這個類的實例都會被Finalizer引用類型引用的砸抛。下文中我們簡稱Finalizer 型的referent為finalizee评雌。

何時調用Finalizer.register生成一個Finalizer類型的reference

Finalizer的構造函數(shù)是private的树枫,也就是不能通過new 來生成一個Fianlizer reference。只能通過靜態(tài)的register方法來生成景东。同時Finalizer有個靜態(tài)字段unfinalized砂轻,維護了一個未執(zhí)行finalize方法的reference列表,在構造函數(shù)中通過add()方法把Finalizer引用本身加入到unfinalized列表中斤吐,同時關聯(lián)finalizee和queue,實現(xiàn)通知機制搔涝。維護靜態(tài)字段unfinalized的目的是為了一直保持對未執(zhí)行finalize方法的reference的強引用,防止被gc回收掉和措。

    private static Finalizer unfinalized = null;
    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        add();
    }
    /* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }
    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

那么register是被VM何時調用的呢庄呈?

JVM通過VM參數(shù) RegisterFinalizersAtInit 的值來確定何時調用register,RegisterFinalizersAtInit默認為true,則會在構造函數(shù)返回之前調用call_register_finalizer方法派阱。

void Parse::return_current(Node* value) {
  if (RegisterFinalizersAtInit &&
      method()->intrinsic_id() == vmIntrinsics::_Object_init) {
    call_register_finalizer();
  }
  …………..
}

如果通過-XX:-RegisterFinalizersAtInit 設為false诬留,則會在對象空間分配好之后就調用call_register_finalizer

instanceOop InstanceKlass::allocate_instance(TRAPS) {
  bool has_finalizer_flag = has_finalizer(); // Query before possible GC
  int size = size_helper();  // Query before forming handle.

  KlassHandle h_k(THREAD, this);

  instanceOop i;

  i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
  if (has_finalizer_flag && !RegisterFinalizersAtInit) {
    i = register_finalizer(i, CHECK_NULL);
  }
  return i;
}

另外需要提醒的是,當我們通過clone的方式復制一個對象時贫母,如果當前類是一個f類文兑,那么在clone完成時將調用Finalizer.register方法進行注冊。

何時入queue

當一個finalizee 只剩Finalizer引用腺劣,沒有其他引用時绿贞,需要被回收了,GC就會把該finalizee對應的reference放到Finalizer的refereneQueue中,等待FinalizerThread來執(zhí)行finalizee的finalize方法橘原,然后finalizee對象才能被GC回收籍铁。

Finalizer問題finalizee對象在finalize重新被賦給一個強引用復活,那么下次GC前會不會被再次執(zhí)行finalize方法?

答案是不會的趾断,runFinalizer中會把該finalizee對應的Finalizer引用從unfinalized隊列中移除寨辩,第二次執(zhí)行的時會通過hasBeenFinalized方法判斷,保證不會被重復執(zhí)行歼冰。

private void runFinalizer(JavaLangAccess jla) {
    synchronized (this) {
        if (hasBeenFinalized()) return;
        remove();
    }
    try {
        Object finalizee = this.get();
        if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
            jla.invokeFinalize(finalizee);

            /* Clear stack slot containing this variable, to decrease
               the chances of false retention with a conservative GC */
            finalizee = null;
        }
    } catch (Throwable x) { }
    super.clear();
}

finalizee至少兩次GC回收才可能被回收靡狞?(質疑,應該是一次)

第一次GC把finalizee對應的Finalizer reference加入referenceQueue等待FinalizerThread來執(zhí)行finalize方法隔嫡。第二次GC才有可能釋放finalizee對象本身甸怕,前提是FinalizerThread已經執(zhí)行完finalize方法了甘穿,并把Finalizer reference從Finalizer靜態(tài)unfinalized鏈表中剔除,因為這個鏈表和Finalizer reference對finalizee構成的是一個強引用梢杭。
正常情況是最少兩次温兼,但是JVM在內存緊張情況下可以立刻回收。

Finalizer 機制導致JVM Full GC 頻繁武契,stop-the-world延長募判?

因為如果finalizee上的finalize方法體執(zhí)行過程耗時比較長,會導致對象一直堆積咒唆,多次GC仍不能釋放届垫,沖進old區(qū),造成Old區(qū)GC過程延長全释,暫停時間增加装处,可能頻繁觸發(fā)Full GC。

總結

通過對SoftReference浸船,WeakReference妄迁,PhantomReference,F(xiàn)inalReference 的介紹李命,可以看出JDK提供這些類型的reference 主要是用來和GC交互的登淘,根據(jù)reference的不同,讓JVM采用不同策略來進行對對象的回收(reclaim)封字。softly-reachable的referent在保證在OutOfMemoryError之前回收對象黔州,weakly-reachable的referent在發(fā)生GC時就會被回收,finalizer型的reference 主要提供GC前對referent進行finalize執(zhí)行機制周叮。同時這些reference和referenceQueue在一起提供通知機制辩撑,PhantomReference的作用就是僅僅就是提供對象回收通知機制,F(xiàn)inalizer借助這種機制實現(xiàn)referent的finalize執(zhí)行仿耽,SoftReference合冀、WeakReference也可以配合referenceQueue使用,實現(xiàn)對象回收通知機制项贺。


image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末君躺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子开缎,更是在濱河造成了極大的恐慌棕叫,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕删,死亡現(xiàn)場離奇詭異俺泣,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門伏钠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來横漏,“玉大人,你說我怎么就攤上這事熟掂《薪剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵赴肚,是天一觀的道長素跺。 經常有香客問我,道長誉券,這世上最難降的妖魔是什么指厌? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮横朋,結果婚禮上仑乌,老公的妹妹穿的比我還像新娘百拓。我一直安慰自己琴锭,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布衙传。 她就那樣靜靜地躺著决帖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蓖捶。 梳的紋絲不亂的頭發(fā)上地回,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音俊鱼,去河邊找鬼刻像。 笑死,一個胖子當著我的面吹牛并闲,可吹牛的內容都是我干的细睡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼帝火,長吁一口氣:“原來是場噩夢啊……” “哼溜徙!你這毒婦竟也來了?” 一聲冷哼從身側響起犀填,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蠢壹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后九巡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體图贸,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疏日。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乏盐。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖制恍,靈堂內的尸體忽然破棺而出父能,到底是詐尸還是另有隱情,我是刑警寧澤净神,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布何吝,位于F島的核電站,受9級特大地震影響鹃唯,放射性物質發(fā)生泄漏爱榕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一坡慌、第九天 我趴在偏房一處隱蔽的房頂上張望黔酥。 院中可真熱鬧,春花似錦洪橘、人聲如沸跪者。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渣玲。三九已至,卻和暖如春弟晚,著一層夾襖步出監(jiān)牢的瞬間忘衍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工卿城, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留枚钓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓瑟押,卻偏偏與公主長得像搀捷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子勉耀,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容

  • java.lang.ref 該包下提供了Reference相關的類指煎,包括基類Reference,三個子類WeakR...
    chandarlee閱讀 2,243評論 1 50
  • 引用類型 JDK1.2之后便斥,Java擴充了引用的概念至壤,將引用分為強引用、軟引用枢纠、弱引用和虛引用四種像街。 強引用類似于...
    德彪閱讀 4,401評論 0 10
  • ReferenceQueue 引用隊列镰绎,在檢測到適當?shù)目傻竭_性更改后脓斩,垃圾回收器將已注冊的引用對象添加到該隊列中 ...
    tomas家的小撥浪鼓閱讀 36,098評論 10 59
  • java 引用類型: 強,軟畴栖,弱随静,虛,引用對象的能力依次減弱吗讶。 強引用 (FinalReference) 這里 a...
    吃花生的小猴子閱讀 249評論 0 0
  • 母親(八十八) 文||與你相識 滿天的星星亮了 您可曾看見 溫情的期盼里 哪雙是屬于我的眼 夜色已覆蓋了世界 我的...
    與你相識_40fa閱讀 218評論 2 7