欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java?DirectByteBuffer堆外內(nèi)存回收詳解

 更新時(shí)間:2022年10月09日 08:23:59   作者:shanml  
這篇文章主要為大家詳細(xì)介紹了Java中發(fā)DirectByteBuffer堆外內(nèi)存回收,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下

PhantomReference虛引用

在分析堆外內(nèi)存回收之前,先了解下PhantomReference虛引用。

PhantomReference需要與ReferenceQueue引用隊(duì)列結(jié)合使用,在GC進(jìn)行垃圾回收的時(shí)候,如果發(fā)現(xiàn)一個(gè)對(duì)象只有虛引用在引用它,則認(rèn)為該對(duì)象需要被回收,會(huì)將引用該對(duì)象的虛引用加入到與其關(guān)聯(lián)的ReferenceQueue隊(duì)列中,開發(fā)者可以通過ReferenceQueue獲取需要被回收的對(duì)象,然后做一些清理操作,從隊(duì)列中獲取過的元素會(huì)從隊(duì)列中清除,之后GC就可以對(duì)該對(duì)象進(jìn)行回收。

虛引用提供了一種追蹤對(duì)象垃圾回收狀態(tài)的機(jī)制,讓開發(fā)者知道哪些對(duì)象準(zhǔn)備進(jìn)行回收,在回收之前開發(fā)者可以進(jìn)行一些清理工作,之后GC就可以將對(duì)象進(jìn)行真正的回收。

來看一個(gè)虛引用的使用例子:

  • 創(chuàng)建一個(gè)ReferenceQueue隊(duì)列queue,用于跟蹤對(duì)象的回收;
  • 創(chuàng)建一個(gè)obj對(duì)象,通過new創(chuàng)建的是強(qiáng)引用,只要強(qiáng)引用存在,對(duì)象就不會(huì)被回收;
  • 創(chuàng)建一個(gè)虛引用PhantomReference,將obj對(duì)象和ReferenceQueue隊(duì)列傳入,此時(shí)phantomReference里面引用了obj對(duì)象,并關(guān)聯(lián)著引用隊(duì)列queue;
  • 同樣的方式創(chuàng)建另一個(gè)obj1對(duì)象和虛引用對(duì)象phantomReference1;
  • 將obj置為NULL,此時(shí)強(qiáng)引用關(guān)系失效;
  • 調(diào)用System.gc()進(jìn)行垃圾回收;
  • 由于obj的強(qiáng)引用關(guān)系失效,所以GC認(rèn)為該對(duì)象需要被回收,會(huì)將引用該對(duì)象的虛引用phantomReference對(duì)象放入到與其關(guān)聯(lián)的引用隊(duì)列queue中;
  • 通過poll從引用隊(duì)列queue中獲取對(duì)象,可以發(fā)現(xiàn)會(huì)獲取到phantomReference對(duì)象,poll獲取之后會(huì)將對(duì)象從引用隊(duì)列中刪除,之后會(huì)被垃圾回收器回收;
  • obj1的強(qiáng)引用關(guān)系還在,所以從queue中并不會(huì)獲取到;
   public static void main(String[] args) {
        // 創(chuàng)建引用隊(duì)列
        ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
        // 創(chuàng)建obj對(duì)象
        Object obj = new Object();
        // 創(chuàng)建虛引用,虛引用引用了obj對(duì)象,并與queue進(jìn)行關(guān)聯(lián)
        PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, queue);
        // 創(chuàng)建obj1對(duì)象
        Object obj1 = new Object();
        PhantomReference<Object> phantomReference1 = new PhantomReference<Object>(obj1, queue);
        // 將obj置為NULL,強(qiáng)引用關(guān)系失效
        obj = null;
        // 垃圾回收
        System.gc();
        // 從引用隊(duì)列獲取對(duì)象
        Object o = queue.poll();
        if (null != o) {
            System.out.println(o.toString());
        }
    }

輸出結(jié)果:

java.lang.ref.PhantomReference@277c0f21

Reference實(shí)例的幾種狀態(tài)

Active:初始狀態(tài),創(chuàng)建一個(gè)Reference類型的實(shí)例之后處于Active狀態(tài),以上面虛引用為例,通過new創(chuàng)建一個(gè)PhantomReference虛引用對(duì)象之后,虛引用對(duì)象就處于Active狀態(tài)。

Pending:當(dāng)GC檢測(cè)到對(duì)象的可達(dá)性發(fā)生變化時(shí),會(huì)根據(jù)是否關(guān)聯(lián)了引用隊(duì)列來決定是否將狀態(tài)更改為Pending或者Inactive,虛引用必須與引用隊(duì)列結(jié)合使用,所以對(duì)于虛引用來說,如果它實(shí)際引用的對(duì)象需要被回收,垃圾回收器會(huì)將這個(gè)虛引用對(duì)象加入到一個(gè)Pending列表中,此時(shí)處于Pending狀態(tài)。

同樣以上面的的虛引用為例,因?yàn)閛bj的強(qiáng)引用關(guān)系失效,GC就會(huì)把引用它的虛引用對(duì)象放入到pending列表中。

Enqueued:表示引用對(duì)象被加入到了引用隊(duì)列,Reference有一個(gè)后臺(tái)線程去檢測(cè)是否有處于Pending狀態(tài)的引用對(duì)象,如果有會(huì)將引用對(duì)象加入到與其關(guān)聯(lián)的引用隊(duì)列中,此時(shí)由Pending轉(zhuǎn)為Enqueued狀態(tài)表示對(duì)象已加入到引用隊(duì)列中。

Inactive:通過引用隊(duì)列的poll方法可以從引用隊(duì)列中獲取引用對(duì)象,同時(shí)引用對(duì)象會(huì)從隊(duì)列中移除,此時(shí)引用對(duì)象處于Inactive狀態(tài),之后會(huì)被GC回收。

DirectByteBuffer堆外內(nèi)存回收

DirectByteBuffer的構(gòu)造函數(shù)中,在申請(qǐng)內(nèi)存之前,先調(diào)用了BitsreserveMemory方法回收內(nèi)存,申請(qǐng)內(nèi)存之后,調(diào)用Cleanercreate方法創(chuàng)建了一個(gè)Cleaner對(duì)象,并傳入了當(dāng)前對(duì)象(DirectByteBuffer)和一個(gè)Deallocator類型的對(duì)象:

class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
    private final Cleaner cleaner;
        
    DirectByteBuffer(int cap) {                   // package-private
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        // 清理內(nèi)存
        Bits.reserveMemory(size, cap);
        long base = 0;
        try {
            // 分配內(nèi)存
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        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;
        }
        // 創(chuàng)建Cleader,傳入了當(dāng)前對(duì)象和Deallocator
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }
}

Cleaner從名字上可以看出與清理有關(guān),BitsreserveMemory方法底層也是通過Cleaner來進(jìn)行清理,所以Cleaner是重點(diǎn)關(guān)注的類。

DeallocatorDirectByteBuffer的一個(gè)內(nèi)部類,并且實(shí)現(xiàn)了Runnable接口,在run方法中可以看到對(duì)內(nèi)存進(jìn)行了釋放,接下來就去看下在哪里觸發(fā)Deallocator任務(wù)的執(zhí)行:

class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {

    private static class Deallocator implements Runnable {
        // ...
        
        private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address; // 設(shè)置內(nèi)存地址
            this.size = size;
            this.capacity = capacity;
        }

        public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            // 釋放內(nèi)存
            unsafe.freeMemory(address);
            address = 0;
            Bits.unreserveMemory(size, capacity);
        }

    }
}

Cleaner

Cleaner繼承了PhantomReference,PhantomReferenceReference的子類,所以Cleaner是一個(gè)虛引用對(duì)象。

創(chuàng)建Cleaner

虛引用需要與引用隊(duì)列結(jié)合使用,所以在Cleaner中可以看到有一個(gè)ReferenceQueue,它是一個(gè)靜態(tài)的變量,所以創(chuàng)建的所有Cleaner對(duì)象都會(huì)共同使用這個(gè)引用隊(duì)列。

在創(chuàng)建Cleaner的create方法中,處理邏輯如下:

  • 通過構(gòu)造函數(shù)創(chuàng)建了一個(gè)Cleaner對(duì)象,構(gòu)造函數(shù)中的referent參數(shù)為DirectByteBuffer,thunk參數(shù)為Deallocator對(duì)象,在構(gòu)造函數(shù)中又調(diào)用了父類的構(gòu)造函數(shù)完成實(shí)例化;
  • 調(diào)用add方法將創(chuàng)建的Cleaner對(duì)象加入到鏈表中,添加到鏈表的時(shí)候使用的是頭插法,新加入的節(jié)點(diǎn)放在鏈表的頭部,first成員變量是一個(gè)靜態(tài)變量,它指向鏈表的頭結(jié)點(diǎn),創(chuàng)建的Cleaner都會(huì)加入到這個(gè)鏈表中;

創(chuàng)建后的Cleaner對(duì)象處于Active狀態(tài)。

 public class Cleaner extends PhantomReference<Object>{

    // ReferenceQueue隊(duì)列
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();

    // 靜態(tài)變量,鏈表的頭結(jié)點(diǎn),創(chuàng)建的Cleaner都會(huì)加入到這個(gè)鏈表中
    static private Cleaner first = null;
     
    // thunk
    private final Runnable thunk;
     
    public static Cleaner create(Object ob, Runnable thunk) {
        if (thunk == null)
            return null;
        // 創(chuàng)建一個(gè)Cleaner并加入鏈表
        return add(new Cleaner(ob, thunk));
    }
    
    private Cleaner(Object referent, Runnable thunk) {
        super(referent, dummyQueue); // 調(diào)用父類構(gòu)造函數(shù),傳入引用對(duì)象和引用隊(duì)列
        this.thunk = thunk; // thunk指向傳入的Deallocator
    }
     
    private static synchronized Cleaner add(Cleaner cl) {
        // 如果頭結(jié)點(diǎn)不為空
        if (first != null) {
            // 將新加入的節(jié)點(diǎn)作為頭結(jié)點(diǎn)
            cl.next = first; 
            first.prev = cl;
        }
        first = cl;
        return cl;
    }
}

Cleaner調(diào)用父類構(gòu)造函數(shù)時(shí),最終會(huì)進(jìn)入到父類Reference中的構(gòu)造函數(shù)中:

referent:指向?qū)嶋H的引用對(duì)象,上面創(chuàng)建的是DirectByteBuffer,所以這里指向的是DirectByteBuffer。

queue:引用隊(duì)列,指向Cleaner中的引用隊(duì)列dummyQueue。

public class PhantomReference<T> extends Reference<T> {
    // ...
    
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q); // 調(diào)用父類構(gòu)造函數(shù)
    }

}

public abstract class Reference<T> {
    /* 引用對(duì)象 */
    private T referent;         
    // 引用隊(duì)列
    volatile ReferenceQueue<? super T> queue;
    
    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        // 設(shè)置引用隊(duì)列
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

}

啟動(dòng)ReferenceHandler線程

Reference中有一個(gè)靜態(tài)方法,里面創(chuàng)建了一個(gè)ReferenceHandler并設(shè)置為守護(hù)線程,然后啟動(dòng)了該線程,并創(chuàng)建了JavaLangRefAccess對(duì)象設(shè)置到SharedSecrets中:

public abstract class Reference<T> {
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        // 創(chuàng)建ReferenceHandler
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        // 設(shè)置優(yōu)先級(jí)為最高
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();

        // 這里設(shè)置了JavaLangRefAccess
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                // 調(diào)用了tryHandlePending
                return tryHandlePending(false);
            }
        });
    }
}

ReferenceHandlerReference的內(nèi)部類,繼承了Thread,在run方法中開啟了一個(gè)循環(huán),不斷的執(zhí)行tryHandlePending方法,處理Reference中pending列表:

public abstract class Reference<T> {
    
    private static class ReferenceHandler extends Thread {
        
        // ...
        
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                // 處理pending列表
                tryHandlePending(true);
            }
        }
    }
 }

Cleaner會(huì)啟動(dòng)一個(gè)優(yōu)先級(jí)最高的守護(hù)線程,不斷調(diào)用tryHandlePending來檢測(cè)是否有需要回收的引用對(duì)象(還未進(jìn)行真正的回收),然后進(jìn)行處理。

處理pending列表

垃圾回收器會(huì)將要回收的引用對(duì)象放在Referencepending變量中,從數(shù)據(jù)類型上可以看出pending只是一個(gè)Reference類型的對(duì)象,并不是一個(gè)list,如果有多個(gè)需要回收的對(duì)象,如何將它們?nèi)糠湃?code>pending對(duì)象中?

可以把pengding看做是一個(gè)鏈表的頭結(jié)點(diǎn),假如有引用對(duì)象被判定需要回收,如果pengding為空直接放入即可,如果不為空,將使用頭插法將新的對(duì)象加入到鏈表中,也就是將新對(duì)象的discovered指向pending對(duì)象,然后將pending指向當(dāng)前要回收的這個(gè)對(duì)象,這樣就形成了一個(gè)鏈表,pending指向鏈表的頭結(jié)點(diǎn)。

在pending鏈表中的引用對(duì)象處于pending狀態(tài)。

接下來看tryHandlePending方法的處理邏輯:

如果pending不為空,表示有需要回收的對(duì)象,此時(shí)將pengding指向的對(duì)象放在臨時(shí)變量r中,并判斷是否是Cleaner類型,如果是將其強(qiáng)制轉(zhuǎn)為Cleaner,記錄在臨時(shí)變量c中,接著更新pending的值為r的discovered,因?yàn)閐iscovered中記錄了下一個(gè)需要被回收的對(duì)象,pengding需要指向下一個(gè)需要被回收的對(duì)象;

pending如果為NULL,會(huì)進(jìn)入到else的處理邏輯,返回值為參數(shù)傳入的waitForNotify的值。

判斷Cleaner對(duì)象是否為空,如果不為空,調(diào)用Cleaner的clean方法進(jìn)行清理;

獲取引用對(duì)象關(guān)聯(lián)的引用隊(duì)列,然后調(diào)用enqueue方法將引用對(duì)象加入到引用隊(duì)列中;

返回true;

public abstract class Reference<T> {
  
    // 指向pending列表中的下一個(gè)節(jié)點(diǎn)
    transient private Reference<T> discovered; 

    // 靜態(tài)變量pending列表,可以看做是一個(gè)鏈表,pending指向鏈表的頭結(jié)點(diǎn)
    private static Reference<Object> pending = null;
  
    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                // 如果pending不為空
                if (pending != null) {
                    // 獲取pending執(zhí)行的對(duì)象
                    r = pending;
                    // 如果是Cleaner類型
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // 將pending指向下一個(gè)節(jié)點(diǎn)
                    pending = r.discovered;
                    // 將discovered置為空
                    r.discovered = null;
                } else {
                    // 等待
                    if (waitForNotify) {
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }
        if (c != null) {
            // 調(diào)用clean方法進(jìn)行清理
            c.clean();
            return true;
        }
        // 獲取引用隊(duì)列
        ReferenceQueue<? super Object> q = r.queue;
        // 如果隊(duì)列不為空,將對(duì)象加入到引用隊(duì)列中
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        // 返回true
        return true;
    }
}

釋放內(nèi)存

Cleaner的clean方法中,可以看到,調(diào)用了thunk的run方法,前面內(nèi)容可知,thunk指向的是Deallocator對(duì)象,所以會(huì)執(zhí)行Deallocator的run方法,Deallocator的run方法前面也已經(jīng)看過,里面會(huì)對(duì)DirectByteBuffer的堆外內(nèi)存進(jìn)行釋放

public class Cleaner extends PhantomReference<Object> {

    public void clean() {
        if (!remove(this))
            return;
        try {
            // 調(diào)用run方法
            thunk.run();
        } catch (final Throwable x) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null)
                            new Error("Cleaner terminated abnormally", x)
                                .printStackTrace();
                        System.exit(1);
                        return null;
                    }});
        }
    }
}

總結(jié)

Cleaner是一個(gè)虛引用,它實(shí)際引用的對(duì)象DirectByteBuffer如果被GC判定為需要回收,會(huì)將引用該對(duì)象的Cleaner加入到pending列表,ReferenceHandler線程會(huì)不斷檢測(cè)pending是否為空,如果不為空,就對(duì)其進(jìn)行處理:

  • 如果對(duì)象類型為Cleaner,就調(diào)用Cleaner的clean方法進(jìn)行清理,Cleaner的clean方法又會(huì)調(diào)用Deallocator的run方法,里面調(diào)用了freeMemory方法對(duì)DirectByteBuffer分配的堆外內(nèi)存進(jìn)行釋放;
  • 將Cleaner對(duì)象加入到與其關(guān)聯(lián)的引用隊(duì)列中;

引用隊(duì)列

ReferenceQueue名字聽起來是一個(gè)隊(duì)列,實(shí)際使用了一個(gè)鏈表,使用頭插法將加入的節(jié)點(diǎn)串起來,ReferenceQueue中的head變量指向鏈表的頭節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)是一個(gè)Reference類型的對(duì)象:

public class ReferenceQueue<T> {

    // head為鏈表頭節(jié)點(diǎn)
    private volatile Reference<? extends T> head = null;
}

Reference中除了discovered變量之外,還有一個(gè)next變量,discovered指向的是處于pending狀態(tài)時(shí)pending列表中的下一個(gè)元素,next變量指向的是處于Enqueued狀態(tài)時(shí),引用隊(duì)列中的下一個(gè)元素:

public abstract class Reference<T> {

    /* When active:   處于active狀態(tài)時(shí)為NULL
     *     pending:   this
     *    Enqueued:   Enqueued狀態(tài)時(shí),指向引用隊(duì)列中的下一個(gè)元素
     *    Inactive:   this
     */
    @SuppressWarnings("rawtypes")
    Reference next;
    
    /* When active:   active狀態(tài)時(shí),指向GC維護(hù)的一個(gè)discovered鏈表中的下一個(gè)元素
     *     pending:   pending狀態(tài)時(shí),指向pending列表中的下一個(gè)元素
     *   otherwise:   其他情況為NULL
     */
    transient private Reference<T> discovered;  /* used by VM */
}

enqueue入隊(duì)

進(jìn)入引用隊(duì)列中的引用對(duì)象處于enqueue狀態(tài)。

enqueue的處理邏輯如下:

  • 判斷要加入的對(duì)象關(guān)聯(lián)的引用隊(duì)列,對(duì)隊(duì)列進(jìn)行判斷,如果隊(duì)列為空或者隊(duì)列等于ReferenceQueue中的空隊(duì)列ENQUEUED,表示該對(duì)象之前已經(jīng)加入過隊(duì)列,不能重復(fù)操作,返回false,如果未加入過繼續(xù)下一步;
  • 將對(duì)象所關(guān)聯(lián)的引用隊(duì)列置為ENQUEUED,它是一個(gè)空隊(duì)列,表示節(jié)點(diǎn)已經(jīng)加入到隊(duì)列中;
  • 判斷頭節(jié)點(diǎn)是否為空,如果為空,表示鏈表還沒有節(jié)點(diǎn),將當(dāng)前對(duì)象的next指向自己,如果頭結(jié)點(diǎn)不為空,將當(dāng)前對(duì)象的next指向頭結(jié)點(diǎn),然后更新頭結(jié)點(diǎn)的值為當(dāng)前對(duì)象(頭插法插入鏈表);
  • 增加隊(duì)列的長(zhǎng)度,也就是鏈表的長(zhǎng)度;
public class ReferenceQueue<T> {

    // 空隊(duì)列
    static ReferenceQueue<Object> ENQUEUED = new Null<>();
    
    // 入隊(duì),將節(jié)點(diǎn)加入引用隊(duì)列,隊(duì)列實(shí)際上是一個(gè)鏈表
    boolean enqueue(Reference<? extends T> r) {
        synchronized (lock) {
            // 獲取關(guān)聯(lián)的引用隊(duì)列
            ReferenceQueue<?> queue = r.queue;
            // 如果為空或者已經(jīng)添加到過隊(duì)列
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            // 將引用隊(duì)列置為一個(gè)空隊(duì)列,表示該節(jié)點(diǎn)已經(jīng)入隊(duì)
            r.queue = ENQUEUED;
            // 如果頭結(jié)點(diǎn)為空將下一個(gè)節(jié)點(diǎn)置為自己,否則將next置為鏈表的頭結(jié)點(diǎn),可以看出同樣使用的是頭插法將節(jié)點(diǎn)插入鏈表
            r.next = (head == null) ? r : head;
            // 更新頭結(jié)點(diǎn)為當(dāng)前節(jié)點(diǎn)
            head = r;
            // 增加長(zhǎng)度
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }
}

poll出隊(duì)

在調(diào)用poll方法從引用隊(duì)列中獲取一個(gè)元素并出隊(duì)的時(shí)候,首先對(duì)head頭結(jié)點(diǎn)進(jìn)行判空,如果為空表示引用隊(duì)列中沒有數(shù)據(jù),返回NULL,否則調(diào)用reallyPoll從引用隊(duì)列中獲取元素。

出隊(duì)的處理邏輯如下:

  • 獲取鏈表中的第一個(gè)節(jié)點(diǎn)也就是頭結(jié)點(diǎn),如果不為空進(jìn)行下一步;
  • 如果頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)是自己,表示鏈表只有一個(gè)節(jié)點(diǎn),頭結(jié)點(diǎn)出隊(duì)之后鏈表為空,所以將頭結(jié)點(diǎn)的值更新為NULL;
  • 如果頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)不是自己,表示鏈表中還有其他節(jié)點(diǎn),更新head頭節(jié)點(diǎn)的值為下一個(gè)節(jié)點(diǎn),也就是next指向的對(duì)象;
  • 將需要出隊(duì)的節(jié)點(diǎn)的引用隊(duì)列置為NULL,next節(jié)點(diǎn)置為自己,表示節(jié)點(diǎn)已從隊(duì)列中刪除;
  • 引用隊(duì)列的長(zhǎng)度減一;
  • 返回要出隊(duì)的節(jié)點(diǎn);

從出隊(duì)的邏輯中可以看出,引用隊(duì)列中的對(duì)象是后進(jìn)先出的,poll出隊(duì)之后的引用對(duì)象處于Inactive狀態(tài),表示可以被GC回收掉。

public class ReferenceQueue<T> {
    /**
     * 從引用隊(duì)列中獲取一個(gè)節(jié)點(diǎn),進(jìn)行出隊(duì)操作
     */
    public Reference<? extends T> poll() {
        // 如果頭結(jié)點(diǎn)為空,表示沒有數(shù)據(jù) 
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
    
    @SuppressWarnings("unchecked")
    private Reference<? extends T> reallyPoll() {     、  /* Must hold lock */
        // 獲取頭結(jié)點(diǎn)
        Reference<? extends T> r = head;
        if (r != null) {
            // 如果頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)是自己,表示鏈表只有一個(gè)節(jié)點(diǎn),head置為null,否則head值為r的下一個(gè)節(jié)點(diǎn),也就是next指向的對(duì)象
            head = (r.next == r) ?
                null :
                r.next;
            // 將引用隊(duì)列置為NULL
            r.queue = NULL;
            // 下一個(gè)節(jié)點(diǎn)置為自己
            r.next = r;
            // 長(zhǎng)度減一
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            // 返回鏈表中的第一個(gè)節(jié)點(diǎn)
            return r;
        }
        return null;
    }
}

reserveMemory內(nèi)存清理

最開始在DirectByteBuffer的構(gòu)造函數(shù)中看到申請(qǐng)內(nèi)存之前會(huì)調(diào)用Bits的reserveMemory方法,如果沒有足夠的內(nèi)存,它會(huì)從SharedSecrets獲取JavaLangRefAccess對(duì)象進(jìn)行一些處理,由前面的內(nèi)容可知,Reference中的靜態(tài)方法啟動(dòng)ReferenceHandler之后,創(chuàng)建了JavaLangRefAccess并設(shè)置到SharedSecrets中,所以這里調(diào)用JavaLangRefAccesstryHandlePendingReference實(shí)際上依舊調(diào)用的是Reference中的tryHandlePending方法。

在調(diào)用Reference中的tryHandlePending方法處理需要回收的對(duì)象之后,調(diào)用tryReserveMemory方法判斷是否有足夠的內(nèi)存,如果內(nèi)存依舊不夠,會(huì)調(diào)用` System.gc()觸發(fā)垃圾回收,然后開啟一個(gè)循環(huán),處理邏輯如下:

1.判斷內(nèi)存是否充足,如果充足直接返回;

2.判斷睡眠次數(shù)是否小于限定的最大值,如果小于繼續(xù)下一步,否則終止循環(huán);

3.調(diào)用tryHandlePendingReference處理penging列表中的引用對(duì)象,前面在處理pending列表的邏輯中可以知道,如果pending列表不為空,會(huì)返回true,tryHandlePendingReference也會(huì)返回true,此時(shí)意味著清理了一部分對(duì)象,所以重新進(jìn)入到第1步進(jìn)行檢查;

如果pending列表為空,會(huì)返回參數(shù)中傳入的waitForNotify的值,從JavaLangRefAccess的tryHandlePendingReference中可以看出這里傳入的是false,所以會(huì)進(jìn)行如下處理:

  • 通過Thread.sleep(sleepTime)讓當(dāng)前線程睡眠一段時(shí)間,這樣可以避免reserveMemory方法一直在占用資源;
  • 對(duì)睡眠次數(shù)加1;

4.如果以上步驟處理之后還沒有足夠的空間會(huì)拋出拋出OutOfMemoryError異常;

reserveMemory方法的作用是保證在申請(qǐng)內(nèi)存之前有足夠的內(nèi)存,如果沒有足夠的內(nèi)存會(huì)進(jìn)行清理,達(dá)到指定清理次數(shù)之后依舊沒有足夠的內(nèi)存空間,將拋出OutOfMemoryError異常。

class Bits {

    static void reserveMemory(long size, int cap) {

        if (!memoryLimitSet && VM.isBooted()) {
            maxMemory = VM.maxDirectMemory();
            memoryLimitSet = true;
        }

        // 是否有足夠內(nèi)存
        if (tryReserveMemory(size, cap)) {
            return;
        }
        // 獲取JavaLangRefAccess
        final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
        // 調(diào)用tryHandlePendingReference
        while (jlra.tryHandlePendingReference()) {
            // 判斷是否有足夠的內(nèi)存
            if (tryReserveMemory(size, cap)) {
                return;
            }
        }

        // 調(diào)用gc進(jìn)行垃圾回收
        System.gc();

        boolean interrupted = false;
        try {
            long sleepTime = 1;
            int sleeps = 0;
            // 開啟循環(huán)
            while (true) {
                // 是否有足夠內(nèi)存
                if (tryReserveMemory(size, cap)) {
                    return;
                }
                // 如果次數(shù)小于最大限定次數(shù),終止
                if (sleeps >= MAX_SLEEPS) {
                    break;
                }
                // 再次處理penging列表中的對(duì)象
                if (!jlra.tryHandlePendingReference()) {
                    try {
                        // 睡眠一段時(shí)間
                        Thread.sleep(sleepTime);
                        sleepTime <<= 1;
                        sleeps++; // 睡眠次數(shù)增加1
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            }
            // 拋出OutOfMemoryError異常
            throw new OutOfMemoryError("Direct buffer memory");

        } finally {
            if (interrupted) {
                // don't swallow interrupts
                Thread.currentThread().interrupt();
            }
        }
    }
}

public abstract class Reference<T> {
    static {
        // ...
        // 這里設(shè)置了JavaLangRefAccess
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                // 調(diào)用tryHandlePending,這里waitForNotify參數(shù)傳入的是false
                return tryHandlePending(false);
            }
        });
    }
}

以上就是Java DirectByteBuffer堆外內(nèi)存回收詳解的詳細(xì)內(nèi)容,更多關(guān)于Java DirectByteBuffer堆外內(nèi)存回收的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring Boot構(gòu)建系統(tǒng)安全層的步驟

    Spring Boot構(gòu)建系統(tǒng)安全層的步驟

    這篇文章主要介紹了Spring Boot構(gòu)建系統(tǒng)安全層的步驟,幫助大家更好的理解和學(xué)習(xí)使用Spring Boot框架,感興趣的朋友可以了解下
    2021-04-04
  • Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法

    Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法

    這篇文章主要介紹了Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法,需要的朋友可以參考下
    2015-11-11
  • 淺談Hibernate對(duì)象狀態(tài)之間的神奇轉(zhuǎn)換

    淺談Hibernate對(duì)象狀態(tài)之間的神奇轉(zhuǎn)換

    這篇文章主要介紹了淺談Hibernate對(duì)象狀態(tài)之間的神奇轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Spring Boot與React集成的示例代碼

    Spring Boot與React集成的示例代碼

    這篇文章主要介紹了Spring Boot與React集成的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • Java Hibernate對(duì)象(瞬時(shí)態(tài),持久態(tài),脫管態(tài))詳解

    Java Hibernate對(duì)象(瞬時(shí)態(tài),持久態(tài),脫管態(tài))詳解

    這篇文章主要介紹了Java Hibernate對(duì)象(瞬時(shí)態(tài),持久態(tài),脫管態(tài))詳解的相關(guān)資料,這里對(duì)Java Hibernate對(duì)象進(jìn)行了介紹及總結(jié),需要的朋友可以參考下
    2016-11-11
  • http中g(shù)et請(qǐng)求與post請(qǐng)求區(qū)別及如何選擇

    http中g(shù)et請(qǐng)求與post請(qǐng)求區(qū)別及如何選擇

    這篇文章主要介紹了http中g(shù)et請(qǐng)求與post請(qǐng)求在應(yīng)用中應(yīng)該如何選擇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2021-09-09
  • Java反射機(jī)制用法總結(jié)

    Java反射機(jī)制用法總結(jié)

    反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語(yǔ)言的反射機(jī)制。下面我們來一起學(xué)習(xí)一下吧
    2019-05-05
  • Java8語(yǔ)法糖之Lambda表達(dá)式的深入講解

    Java8語(yǔ)法糖之Lambda表達(dá)式的深入講解

    這篇文章主要給大家介紹了關(guān)于Java8語(yǔ)法糖之Lambda表達(dá)式的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • 詳解Spring-bean的循環(huán)依賴以及解決方式

    詳解Spring-bean的循環(huán)依賴以及解決方式

    這篇文章主要介紹了詳解Spring-bean的循環(huán)依賴以及解決方式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限的示例代碼

    Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限的示例代碼

    在我們?nèi)粘i_發(fā)過程中,通常會(huì)涉及到數(shù)據(jù)權(quán)限問題,本文主要介紹了Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評(píng)論