Java?DirectByteBuffer堆外內(nèi)存回收詳解
PhantomReference虛引用
在分析堆外內(nèi)存回收之前,先了解下PhantomReference虛引用。
PhantomReference需要與ReferenceQueue引用隊列結(jié)合使用,在GC進(jìn)行垃圾回收的時候,如果發(fā)現(xiàn)一個對象只有虛引用在引用它,則認(rèn)為該對象需要被回收,會將引用該對象的虛引用加入到與其關(guān)聯(lián)的ReferenceQueue隊列中,開發(fā)者可以通過ReferenceQueue獲取需要被回收的對象,然后做一些清理操作,從隊列中獲取過的元素會從隊列中清除,之后GC就可以對該對象進(jìn)行回收。
虛引用提供了一種追蹤對象垃圾回收狀態(tài)的機(jī)制,讓開發(fā)者知道哪些對象準(zhǔn)備進(jìn)行回收,在回收之前開發(fā)者可以進(jìn)行一些清理工作,之后GC就可以將對象進(jìn)行真正的回收。
來看一個虛引用的使用例子:
- 創(chuàng)建一個
ReferenceQueue隊列queue,用于跟蹤對象的回收; - 創(chuàng)建一個obj對象,通過new創(chuàng)建的是強(qiáng)引用,只要強(qiáng)引用存在,對象就不會被回收;
- 創(chuàng)建一個虛引用
PhantomReference,將obj對象和ReferenceQueue隊列傳入,此時phantomReference里面引用了obj對象,并關(guān)聯(lián)著引用隊列queue; - 同樣的方式創(chuàng)建另一個obj1對象和虛引用對象phantomReference1;
- 將obj置為NULL,此時強(qiáng)引用關(guān)系失效;
- 調(diào)用
System.gc()進(jìn)行垃圾回收; - 由于obj的強(qiáng)引用關(guān)系失效,所以GC認(rèn)為該對象需要被回收,會將引用該對象的虛引用phantomReference對象放入到與其關(guān)聯(lián)的引用隊列queue中;
- 通過
poll從引用隊列queue中獲取對象,可以發(fā)現(xiàn)會獲取到phantomReference對象,poll獲取之后會將對象從引用隊列中刪除,之后會被垃圾回收器回收; - obj1的強(qiáng)引用關(guān)系還在,所以從queue中并不會獲取到;
public static void main(String[] args) {
// 創(chuàng)建引用隊列
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
// 創(chuàng)建obj對象
Object obj = new Object();
// 創(chuàng)建虛引用,虛引用引用了obj對象,并與queue進(jìn)行關(guān)聯(lián)
PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, queue);
// 創(chuàng)建obj1對象
Object obj1 = new Object();
PhantomReference<Object> phantomReference1 = new PhantomReference<Object>(obj1, queue);
// 將obj置為NULL,強(qiáng)引用關(guān)系失效
obj = null;
// 垃圾回收
System.gc();
// 從引用隊列獲取對象
Object o = queue.poll();
if (null != o) {
System.out.println(o.toString());
}
}
輸出結(jié)果:
java.lang.ref.PhantomReference@277c0f21

Reference實例的幾種狀態(tài)
Active:初始狀態(tài),創(chuàng)建一個Reference類型的實例之后處于Active狀態(tài),以上面虛引用為例,通過new創(chuàng)建一個PhantomReference虛引用對象之后,虛引用對象就處于Active狀態(tài)。
Pending:當(dāng)GC檢測到對象的可達(dá)性發(fā)生變化時,會根據(jù)是否關(guān)聯(lián)了引用隊列來決定是否將狀態(tài)更改為Pending或者Inactive,虛引用必須與引用隊列結(jié)合使用,所以對于虛引用來說,如果它實際引用的對象需要被回收,垃圾回收器會將這個虛引用對象加入到一個Pending列表中,此時處于Pending狀態(tài)。
同樣以上面的的虛引用為例,因為obj的強(qiáng)引用關(guān)系失效,GC就會把引用它的虛引用對象放入到pending列表中。
Enqueued:表示引用對象被加入到了引用隊列,Reference有一個后臺線程去檢測是否有處于Pending狀態(tài)的引用對象,如果有會將引用對象加入到與其關(guān)聯(lián)的引用隊列中,此時由Pending轉(zhuǎn)為Enqueued狀態(tài)表示對象已加入到引用隊列中。
Inactive:通過引用隊列的poll方法可以從引用隊列中獲取引用對象,同時引用對象會從隊列中移除,此時引用對象處于Inactive狀態(tài),之后會被GC回收。
DirectByteBuffer堆外內(nèi)存回收
在DirectByteBuffer的構(gòu)造函數(shù)中,在申請內(nèi)存之前,先調(diào)用了Bits的reserveMemory方法回收內(nèi)存,申請內(nèi)存之后,調(diào)用Cleaner的create方法創(chuàng)建了一個Cleaner對象,并傳入了當(dāng)前對象(DirectByteBuffer)和一個Deallocator類型的對象:
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)前對象和Deallocator
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
}
Cleaner從名字上可以看出與清理有關(guān),Bits的reserveMemory方法底層也是通過Cleaner來進(jìn)行清理,所以Cleaner是重點關(guān)注的類。
Deallocator是DirectByteBuffer的一個內(nèi)部類,并且實現(xiàn)了Runnable接口,在run方法中可以看到對內(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,PhantomReference是Reference的子類,所以Cleaner是一個虛引用對象。

創(chuàng)建Cleaner
虛引用需要與引用隊列結(jié)合使用,所以在Cleaner中可以看到有一個ReferenceQueue,它是一個靜態(tài)的變量,所以創(chuàng)建的所有Cleaner對象都會共同使用這個引用隊列。
在創(chuàng)建Cleaner的create方法中,處理邏輯如下:
- 通過構(gòu)造函數(shù)創(chuàng)建了一個Cleaner對象,構(gòu)造函數(shù)中的referent參數(shù)為
DirectByteBuffer,thunk參數(shù)為Deallocator對象,在構(gòu)造函數(shù)中又調(diào)用了父類的構(gòu)造函數(shù)完成實例化; - 調(diào)用add方法將創(chuàng)建的Cleaner對象加入到鏈表中,添加到鏈表的時候使用的是頭插法,新加入的節(jié)點放在鏈表的頭部,first成員變量是一個靜態(tài)變量,它指向鏈表的頭結(jié)點,創(chuàng)建的Cleaner都會加入到這個鏈表中;
創(chuàng)建后的Cleaner對象處于Active狀態(tài)。
public class Cleaner extends PhantomReference<Object>{
// ReferenceQueue隊列
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
// 靜態(tài)變量,鏈表的頭結(jié)點,創(chuàng)建的Cleaner都會加入到這個鏈表中
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)建一個Cleaner并加入鏈表
return add(new Cleaner(ob, thunk));
}
private Cleaner(Object referent, Runnable thunk) {
super(referent, dummyQueue); // 調(diào)用父類構(gòu)造函數(shù),傳入引用對象和引用隊列
this.thunk = thunk; // thunk指向傳入的Deallocator
}
private static synchronized Cleaner add(Cleaner cl) {
// 如果頭結(jié)點不為空
if (first != null) {
// 將新加入的節(jié)點作為頭結(jié)點
cl.next = first;
first.prev = cl;
}
first = cl;
return cl;
}
}
Cleaner調(diào)用父類構(gòu)造函數(shù)時,最終會進(jìn)入到父類Reference中的構(gòu)造函數(shù)中:
referent:指向?qū)嶋H的引用對象,上面創(chuàng)建的是DirectByteBuffer,所以這里指向的是DirectByteBuffer。
queue:引用隊列,指向Cleaner中的引用隊列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> {
/* 引用對象 */
private T referent;
// 引用隊列
volatile ReferenceQueue<? super T> queue;
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
// 設(shè)置引用隊列
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
}

啟動ReferenceHandler線程
Reference中有一個靜態(tài)方法,里面創(chuàng)建了一個ReferenceHandler并設(shè)置為守護(hù)線程,然后啟動了該線程,并創(chuàng)建了JavaLangRefAccess對象設(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)先級為最高
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);
}
});
}
}
ReferenceHandler是Reference的內(nèi)部類,繼承了Thread,在run方法中開啟了一個循環(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會啟動一個優(yōu)先級最高的守護(hù)線程,不斷調(diào)用tryHandlePending來檢測是否有需要回收的引用對象(還未進(jìn)行真正的回收),然后進(jìn)行處理。
處理pending列表
垃圾回收器會將要回收的引用對象放在Reference的pending變量中,從數(shù)據(jù)類型上可以看出pending只是一個Reference類型的對象,并不是一個list,如果有多個需要回收的對象,如何將它們?nèi)糠湃?code>pending對象中?
可以把pengding看做是一個鏈表的頭結(jié)點,假如有引用對象被判定需要回收,如果pengding為空直接放入即可,如果不為空,將使用頭插法將新的對象加入到鏈表中,也就是將新對象的discovered指向pending對象,然后將pending指向當(dāng)前要回收的這個對象,這樣就形成了一個鏈表,pending指向鏈表的頭結(jié)點。

在pending鏈表中的引用對象處于pending狀態(tài)。
接下來看tryHandlePending方法的處理邏輯:
如果pending不為空,表示有需要回收的對象,此時將pengding指向的對象放在臨時變量r中,并判斷是否是Cleaner類型,如果是將其強(qiáng)制轉(zhuǎn)為Cleaner,記錄在臨時變量c中,接著更新pending的值為r的discovered,因為discovered中記錄了下一個需要被回收的對象,pengding需要指向下一個需要被回收的對象;
pending如果為NULL,會進(jìn)入到else的處理邏輯,返回值為參數(shù)傳入的waitForNotify的值。
判斷Cleaner對象是否為空,如果不為空,調(diào)用Cleaner的clean方法進(jìn)行清理;
獲取引用對象關(guān)聯(lián)的引用隊列,然后調(diào)用enqueue方法將引用對象加入到引用隊列中;
返回true;
public abstract class Reference<T> {
// 指向pending列表中的下一個節(jié)點
transient private Reference<T> discovered;
// 靜態(tài)變量pending列表,可以看做是一個鏈表,pending指向鏈表的頭結(jié)點
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í)行的對象
r = pending;
// 如果是Cleaner類型
c = r instanceof Cleaner ? (Cleaner) r : null;
// 將pending指向下一個節(jié)點
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;
}
// 獲取引用隊列
ReferenceQueue<? super Object> q = r.queue;
// 如果隊列不為空,將對象加入到引用隊列中
if (q != ReferenceQueue.NULL) q.enqueue(r);
// 返回true
return true;
}
}
釋放內(nèi)存
在Cleaner的clean方法中,可以看到,調(diào)用了thunk的run方法,前面內(nèi)容可知,thunk指向的是Deallocator對象,所以會執(zhí)行Deallocator的run方法,Deallocator的run方法前面也已經(jīng)看過,里面會對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是一個虛引用,它實際引用的對象DirectByteBuffer如果被GC判定為需要回收,會將引用該對象的Cleaner加入到pending列表,ReferenceHandler線程會不斷檢測pending是否為空,如果不為空,就對其進(jìn)行處理:
- 如果對象類型為Cleaner,就調(diào)用Cleaner的clean方法進(jìn)行清理,Cleaner的clean方法又會調(diào)用Deallocator的run方法,里面調(diào)用了freeMemory方法對DirectByteBuffer分配的堆外內(nèi)存進(jìn)行釋放;
- 將Cleaner對象加入到與其關(guān)聯(lián)的引用隊列中;
引用隊列
ReferenceQueue名字聽起來是一個隊列,實際使用了一個鏈表,使用頭插法將加入的節(jié)點串起來,ReferenceQueue中的head變量指向鏈表的頭節(jié)點,每個節(jié)點是一個Reference類型的對象:
public class ReferenceQueue<T> {
// head為鏈表頭節(jié)點
private volatile Reference<? extends T> head = null;
}
Reference中除了discovered變量之外,還有一個next變量,discovered指向的是處于pending狀態(tài)時pending列表中的下一個元素,next變量指向的是處于Enqueued狀態(tài)時,引用隊列中的下一個元素:
public abstract class Reference<T> {
/* When active: 處于active狀態(tài)時為NULL
* pending: this
* Enqueued: Enqueued狀態(tài)時,指向引用隊列中的下一個元素
* Inactive: this
*/
@SuppressWarnings("rawtypes")
Reference next;
/* When active: active狀態(tài)時,指向GC維護(hù)的一個discovered鏈表中的下一個元素
* pending: pending狀態(tài)時,指向pending列表中的下一個元素
* otherwise: 其他情況為NULL
*/
transient private Reference<T> discovered; /* used by VM */
}

enqueue入隊
進(jìn)入引用隊列中的引用對象處于enqueue狀態(tài)。
enqueue的處理邏輯如下:
- 判斷要加入的對象關(guān)聯(lián)的引用隊列,對隊列進(jìn)行判斷,如果隊列為空或者隊列等于
ReferenceQueue中的空隊列ENQUEUED,表示該對象之前已經(jīng)加入過隊列,不能重復(fù)操作,返回false,如果未加入過繼續(xù)下一步; - 將對象所關(guān)聯(lián)的引用隊列置為
ENQUEUED,它是一個空隊列,表示節(jié)點已經(jīng)加入到隊列中; - 判斷頭節(jié)點是否為空,如果為空,表示鏈表還沒有節(jié)點,將當(dāng)前對象的next指向自己,如果頭結(jié)點不為空,將當(dāng)前對象的next指向頭結(jié)點,然后更新頭結(jié)點的值為當(dāng)前對象(頭插法插入鏈表);
- 增加隊列的長度,也就是鏈表的長度;
public class ReferenceQueue<T> {
// 空隊列
static ReferenceQueue<Object> ENQUEUED = new Null<>();
// 入隊,將節(jié)點加入引用隊列,隊列實際上是一個鏈表
boolean enqueue(Reference<? extends T> r) {
synchronized (lock) {
// 獲取關(guān)聯(lián)的引用隊列
ReferenceQueue<?> queue = r.queue;
// 如果為空或者已經(jīng)添加到過隊列
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 將引用隊列置為一個空隊列,表示該節(jié)點已經(jīng)入隊
r.queue = ENQUEUED;
// 如果頭結(jié)點為空將下一個節(jié)點置為自己,否則將next置為鏈表的頭結(jié)點,可以看出同樣使用的是頭插法將節(jié)點插入鏈表
r.next = (head == null) ? r : head;
// 更新頭結(jié)點為當(dāng)前節(jié)點
head = r;
// 增加長度
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
}
poll出隊
在調(diào)用poll方法從引用隊列中獲取一個元素并出隊的時候,首先對head頭結(jié)點進(jìn)行判空,如果為空表示引用隊列中沒有數(shù)據(jù),返回NULL,否則調(diào)用reallyPoll從引用隊列中獲取元素。
出隊的處理邏輯如下:
- 獲取鏈表中的第一個節(jié)點也就是頭結(jié)點,如果不為空進(jìn)行下一步;
- 如果頭節(jié)點的下一個節(jié)點是自己,表示鏈表只有一個節(jié)點,頭結(jié)點出隊之后鏈表為空,所以將頭結(jié)點的值更新為NULL;
- 如果頭節(jié)點的下一個節(jié)點不是自己,表示鏈表中還有其他節(jié)點,更新head頭節(jié)點的值為下一個節(jié)點,也就是next指向的對象;
- 將需要出隊的節(jié)點的引用隊列置為NULL,next節(jié)點置為自己,表示節(jié)點已從隊列中刪除;
- 引用隊列的長度減一;
- 返回要出隊的節(jié)點;
從出隊的邏輯中可以看出,引用隊列中的對象是后進(jìn)先出的,poll出隊之后的引用對象處于Inactive狀態(tài),表示可以被GC回收掉。
public class ReferenceQueue<T> {
/**
* 從引用隊列中獲取一個節(jié)點,進(jìn)行出隊操作
*/
public Reference<? extends T> poll() {
// 如果頭結(jié)點為空,表示沒有數(shù)據(jù)
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
@SuppressWarnings("unchecked")
private Reference<? extends T> reallyPoll() { 、 /* Must hold lock */
// 獲取頭結(jié)點
Reference<? extends T> r = head;
if (r != null) {
// 如果頭結(jié)點的下一個節(jié)點是自己,表示鏈表只有一個節(jié)點,head置為null,否則head值為r的下一個節(jié)點,也就是next指向的對象
head = (r.next == r) ?
null :
r.next;
// 將引用隊列置為NULL
r.queue = NULL;
// 下一個節(jié)點置為自己
r.next = r;
// 長度減一
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
// 返回鏈表中的第一個節(jié)點
return r;
}
return null;
}
}
reserveMemory內(nèi)存清理
最開始在DirectByteBuffer的構(gòu)造函數(shù)中看到申請內(nèi)存之前會調(diào)用Bits的reserveMemory方法,如果沒有足夠的內(nèi)存,它會從SharedSecrets獲取JavaLangRefAccess對象進(jìn)行一些處理,由前面的內(nèi)容可知,Reference中的靜態(tài)方法啟動ReferenceHandler之后,創(chuàng)建了JavaLangRefAccess并設(shè)置到SharedSecrets中,所以這里調(diào)用JavaLangRefAccess的tryHandlePendingReference實際上依舊調(diào)用的是Reference中的tryHandlePending方法。
在調(diào)用Reference中的tryHandlePending方法處理需要回收的對象之后,調(diào)用tryReserveMemory方法判斷是否有足夠的內(nèi)存,如果內(nèi)存依舊不夠,會調(diào)用` System.gc()觸發(fā)垃圾回收,然后開啟一個循環(huán),處理邏輯如下:
1.判斷內(nèi)存是否充足,如果充足直接返回;
2.判斷睡眠次數(shù)是否小于限定的最大值,如果小于繼續(xù)下一步,否則終止循環(huán);
3.調(diào)用tryHandlePendingReference處理penging列表中的引用對象,前面在處理pending列表的邏輯中可以知道,如果pending列表不為空,會返回true,tryHandlePendingReference也會返回true,此時意味著清理了一部分對象,所以重新進(jìn)入到第1步進(jìn)行檢查;
如果pending列表為空,會返回參數(shù)中傳入的waitForNotify的值,從JavaLangRefAccess的tryHandlePendingReference中可以看出這里傳入的是false,所以會進(jìn)行如下處理:
- 通過
Thread.sleep(sleepTime)讓當(dāng)前線程睡眠一段時間,這樣可以避免reserveMemory方法一直在占用資源; - 對睡眠次數(shù)加1;
4.如果以上步驟處理之后還沒有足夠的空間會拋出拋出OutOfMemoryError異常;
reserveMemory方法的作用是保證在申請內(nèi)存之前有足夠的內(nèi)存,如果沒有足夠的內(nèi)存會進(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列表中的對象
if (!jlra.tryHandlePendingReference()) {
try {
// 睡眠一段時間
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)存回收的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java訪問WebService返回XML數(shù)據(jù)的方法
這篇文章主要介紹了Java訪問WebService返回XML數(shù)據(jù)的方法,涉及java操作WebService的相關(guān)技巧,需要的朋友可以參考下2015-06-06
使用quartz時,傳入?yún)?shù)到j(luò)ob中的使用記錄
這篇文章主要介紹了使用quartz時,傳入?yún)?shù)到j(luò)ob中的使用記錄,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
java并發(fā)編程專題(十)----(JUC原子類)基本類型詳解
這篇文章主要介紹了java JUC原子類基本類型詳解的相關(guān)資料,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07
使用Springboot實現(xiàn)OAuth服務(wù)的示例詳解
OAuth(Open Authorization)是一個開放標(biāo)準(zhǔn),用于授權(quán)第三方應(yīng)用程序訪問用戶資源,而不需要共享用戶憑證。本文主要介紹了如何使用Springboot實現(xiàn)一個OAuth服務(wù),需要的可以參考一下2023-05-05
PowerJob的HashedWheelTimer工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的HashedWheelTimer工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01

