堆外內(nèi)存
堆外內(nèi)存是相對(duì)于堆內(nèi)內(nèi)存的一個(gè)概念。堆內(nèi)內(nèi)存是由JVM所管控的Java進(jìn)程內(nèi)存乳乌,我們平時(shí)在Java中創(chuàng)建的對(duì)象都處于堆內(nèi)內(nèi)存中,并且它們遵循JVM的內(nèi)存管理機(jī)制逻炊,JVM會(huì)采用垃圾回收機(jī)制統(tǒng)一管理它們的內(nèi)存。那么堆外內(nèi)存就是存在于JVM管控之外的一塊內(nèi)存區(qū)域犁享,因此它是不受JVM的管控余素。
在講解DirectByteBuffer之前,需要先簡(jiǎn)單了解兩個(gè)知識(shí)點(diǎn)
java引用類型炊昆,因?yàn)镈irectByteBuffer是通過虛引用(Phantom Reference)來實(shí)現(xiàn)堆外內(nèi)存的釋放的桨吊。
PhantomReference 是所有“弱引用”中最弱的引用類型。不同于軟引用和弱引用凤巨,虛引用無法通過 get() 方法來取得目標(biāo)對(duì)象的強(qiáng)引用從而使用目標(biāo)對(duì)象视乐,觀察源碼可以發(fā)現(xiàn) get() 被重寫為永遠(yuǎn)返回 null。
那虛引用到底有什么作用磅甩?其實(shí)虛引用主要被用來 跟蹤對(duì)象被垃圾回收的狀態(tài)炊林,通過查看引用隊(duì)列中是否包含對(duì)象所對(duì)應(yīng)的虛引用來判斷它是否 即將被垃圾回收,從而采取行動(dòng)卷要。它并不被期待用來取得目標(biāo)對(duì)象的引用渣聚,而目標(biāo)對(duì)象被回收前,它的引用會(huì)被放入一個(gè) ReferenceQueue 對(duì)象中僧叉,從而達(dá)到跟蹤對(duì)象垃圾回收的作用奕枝。
關(guān)于java引用類型的實(shí)現(xiàn)和原理可以閱讀之前的文章Reference 、ReferenceQueue 詳解 和Java 引用類型簡(jiǎn)述
關(guān)于linux的內(nèi)核態(tài)和用戶態(tài)
- 內(nèi)核態(tài):控制計(jì)算機(jī)的硬件資源瓶堕,并提供上層應(yīng)用程序運(yùn)行的環(huán)境隘道。比如socket I/0操作或者文件的讀寫操作等
- 用戶態(tài):上層應(yīng)用程序的活動(dòng)空間,應(yīng)用程序的執(zhí)行必須依托于內(nèi)核提供的資源郎笆。
- 系統(tǒng)調(diào)用:為了使上層應(yīng)用能夠訪問到這些資源谭梗,內(nèi)核為上層應(yīng)用提供訪問的接口。
因此我們可以得知當(dāng)我們通過JNI調(diào)用的native方法實(shí)際上就是從用戶態(tài)切換到了內(nèi)核態(tài)的一種方式宛蚓。并且通過該系統(tǒng)調(diào)用使用操作系統(tǒng)所提供的功能激捏。
Q:為什么需要用戶進(jìn)程(位于用戶態(tài)中)要通過系統(tǒng)調(diào)用(Java中即使JNI)來調(diào)用內(nèi)核態(tài)中的資源,或者說調(diào)用操作系統(tǒng)的服務(wù)了凄吏?
A:intel cpu提供Ring0-Ring3四種級(jí)別的運(yùn)行模式远舅,Ring0級(jí)別最高,Ring3最低痕钢。Linux使用了Ring3級(jí)別運(yùn)行用戶態(tài)图柏,Ring0作為內(nèi)核態(tài)。Ring3狀態(tài)不能訪問Ring0的地址空間任连,包括代碼和數(shù)據(jù)蚤吹。因此用戶態(tài)是沒有權(quán)限去操作內(nèi)核態(tài)的資源的,它只能通過系統(tǒng)調(diào)用外完成用戶態(tài)到內(nèi)核態(tài)的切換随抠,然后在完成相關(guān)操作后再有內(nèi)核態(tài)切換回用戶態(tài)距辆。
DirectByteBuffer ———— 直接緩沖
DirectByteBuffer是Java用于實(shí)現(xiàn)堆外內(nèi)存的一個(gè)重要類余佃,我們可以通過該類實(shí)現(xiàn)堆外內(nèi)存的創(chuàng)建、使用和銷毀跨算。
DirectByteBuffer該類本身還是位于Java內(nèi)存模型的堆中爆土。堆內(nèi)內(nèi)存是JVM可以直接管控、操縱诸蚕。
而DirectByteBuffer中的unsafe.allocateMemory(size);是個(gè)一個(gè)native方法步势,這個(gè)方法分配的是堆外內(nèi)存,通過C的malloc來進(jìn)行分配的背犯。分配的內(nèi)存是系統(tǒng)本地的內(nèi)存坏瘩,并不在Java的內(nèi)存中,也不屬于JVM管控范圍漠魏,所以在DirectByteBuffer一定會(huì)存在某種方式來操縱堆外內(nèi)存倔矾。
在DirectByteBuffer的父類Buffer中有個(gè)address屬性:
// Used only by direct buffers
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
long address;
address只會(huì)被直接緩存給使用到。之所以將address屬性升級(jí)放在Buffer中柱锹,是為了在JNI調(diào)用GetDirectBufferAddress時(shí)提升它調(diào)用的速率哪自。
address表示分配的堆外內(nèi)存的地址。
unsafe.allocateMemory(size);分配完堆外內(nèi)存后就會(huì)返回分配的堆外內(nèi)存基地址禁熏,并將這個(gè)地址賦值給了address屬性壤巷。這樣我們后面通過JNI對(duì)這個(gè)堆外內(nèi)存操作時(shí)都是通過這個(gè)address來實(shí)現(xiàn)的了。
在前面我們說過瞧毙,在linux中內(nèi)核態(tài)的權(quán)限是最高的胧华,那么在內(nèi)核態(tài)的場(chǎng)景下,操作系統(tǒng)是可以訪問任何一個(gè)內(nèi)存區(qū)域的宙彪,所以操作系統(tǒng)是可以訪問到Java堆的這個(gè)內(nèi)存區(qū)域的矩动。
Q:那為什么操作系統(tǒng)不直接訪問Java堆內(nèi)的內(nèi)存區(qū)域了?
A:這是因?yàn)镴NI方法訪問的內(nèi)存區(qū)域是一個(gè)已經(jīng)確定了的內(nèi)存區(qū)域地質(zhì)释漆,那么該內(nèi)存地址指向的是Java堆內(nèi)內(nèi)存的話悲没,那么如果在操作系統(tǒng)正在訪問這個(gè)內(nèi)存地址的時(shí)候,Java在這個(gè)時(shí)候進(jìn)行了GC操作灵汪,而GC操作會(huì)涉及到數(shù)據(jù)的移動(dòng)操作[GC經(jīng)常會(huì)進(jìn)行先標(biāo)志在壓縮的操作。即柑潦,將可回收的空間做標(biāo)志享言,然后清空標(biāo)志位置的內(nèi)存,然后會(huì)進(jìn)行一個(gè)壓縮渗鬼,壓縮就會(huì)涉及到對(duì)象的移動(dòng)览露,移動(dòng)的目的是為了騰出一塊更加完整、連續(xù)的內(nèi)存空間譬胎,以容納更大的新對(duì)象]差牛,數(shù)據(jù)的移動(dòng)會(huì)使JNI調(diào)用的數(shù)據(jù)錯(cuò)亂命锄。所以JNI調(diào)用的內(nèi)存是不能進(jìn)行GC操作的。
Q:如上面所說偏化,JNI調(diào)用的內(nèi)存是不能進(jìn)行GC操作的脐恩,那該如何解決了?
A:①堆內(nèi)內(nèi)存與堆外內(nèi)存之間數(shù)據(jù)拷貝的方式(并且在將堆內(nèi)內(nèi)存拷貝到堆外內(nèi)存的過程JVM會(huì)保證不會(huì)進(jìn)行GC操作):比如我們要完成一個(gè)從文件中讀數(shù)據(jù)到堆內(nèi)內(nèi)存的操作侦讨,即FileChannelImpl.read(HeapByteBuffer)驶冒。這里實(shí)際上File I/O會(huì)將數(shù)據(jù)讀到堆外內(nèi)存中,然后堆外內(nèi)存再講數(shù)據(jù)拷貝到堆內(nèi)內(nèi)存韵卤,這樣我們就讀到了文件中的內(nèi)存骗污。
static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
if (var1.isReadOnly()) {
throw new IllegalArgumentException("Read-only buffer");
} else if (var1 instanceof DirectBuffer) {
return readIntoNativeBuffer(var0, var1, var2, var4);
} else {
// 分配臨時(shí)的堆外內(nèi)存
ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());
int var7;
try {
// File I/O 操作會(huì)將數(shù)據(jù)讀入到堆外內(nèi)存中
int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
var5.flip();
if (var6 > 0) {
// 將堆外內(nèi)存的數(shù)據(jù)拷貝到堆外內(nèi)存中
var1.put(var5);
}
var7 = var6;
} finally {
// 里面會(huì)調(diào)用DirectBuffer.cleaner().clean()來釋放臨時(shí)的堆外內(nèi)存
Util.offerFirstTemporaryDirectBuffer(var5);
}
return var7;
}
}
而寫操作則反之,我們會(huì)將堆內(nèi)內(nèi)存的數(shù)據(jù)線寫到對(duì)堆外內(nèi)存中沈条,然后操作系統(tǒng)會(huì)將堆外內(nèi)存的數(shù)據(jù)寫入到文件中需忿。
② 直接使用堆外內(nèi)存,如DirectByteBuffer:這種方式是直接在堆外分配一個(gè)內(nèi)存(即蜡歹,native memory)來存儲(chǔ)數(shù)據(jù)屋厘,程序通過JNI直接將數(shù)據(jù)讀/寫到堆外內(nèi)存中。因?yàn)閿?shù)據(jù)直接寫入到了堆外內(nèi)存中季稳,所以這種方式就不會(huì)再在JVM管控的堆內(nèi)再分配內(nèi)存來存儲(chǔ)數(shù)據(jù)了擅这,也就不存在堆內(nèi)內(nèi)存和堆外內(nèi)存數(shù)據(jù)拷貝的操作了。這樣在進(jìn)行I/O操作時(shí)景鼠,只需要將這個(gè)堆外內(nèi)存地址傳給JNI的I/O的函數(shù)就好了仲翎。
DirectByteBuffer堆外內(nèi)存的創(chuàng)建和回收的源碼解讀
堆外內(nèi)存分配
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)存(按頁分配)的大小和實(shí)際內(nèi)存的大小
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 通過unsafe.allocateMemory分配堆外內(nèi)存,并返回堆外內(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;
}
// 構(gòu)建Cleaner對(duì)象用于跟蹤DirectByteBuffer對(duì)象的垃圾回收铛漓,以實(shí)現(xiàn)當(dāng)DirectByteBuffer被垃圾回收時(shí)溯香,堆外內(nèi)存也會(huì)被釋放
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
Bits.reserveMemory(size, cap) 方法
static void reserveMemory(long size, int cap) {
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
// optimist!
if (tryReserveMemory(size, cap)) {
return;
}
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;
}
}
// trigger VM's Reference processing
System.gc();
// a retry loop with exponential back-off delays
// (this gives VM some time to do it's job)
boolean interrupted = false;
try {
long sleepTime = 1;
int sleeps = 0;
while (true) {
if (tryReserveMemory(size, cap)) {
return;
}
if (sleeps >= MAX_SLEEPS) {
break;
}
if (!jlra.tryHandlePendingReference()) {
try {
Thread.sleep(sleepTime);
sleepTime <<= 1;
sleeps++;
} catch (InterruptedException e) {
interrupted = true;
}
}
}
// no luck
throw new OutOfMemoryError("Direct buffer memory");
} finally {
if (interrupted) {
// don't swallow interrupts
Thread.currentThread().interrupt();
}
}
}
該方法用于在系統(tǒng)中保存總分配內(nèi)存(按頁分配)的大小和實(shí)際內(nèi)存的大小。
其中浓恶,如果系統(tǒng)中內(nèi)存( 即玫坛,堆外內(nèi)存 )不夠的話:
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;
}
}
jlra.tryHandlePendingReference()會(huì)觸發(fā)一次非堵塞的Reference#tryHandlePending(false)。該方法會(huì)將已經(jīng)被JVM垃圾回收的DirectBuffer對(duì)象的堆外內(nèi)存釋放包晰。
因?yàn)樵赗eference的靜態(tài)代碼塊中定義了:
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
如果在進(jìn)行一次堆外內(nèi)存資源回收后湿镀,還不夠進(jìn)行本次堆外內(nèi)存分配的話,則
// trigger VM's Reference processing
System.gc();
System.gc()會(huì)觸發(fā)一個(gè)full gc伐憾,當(dāng)然前提是你沒有顯示的設(shè)置-XX:+DisableExplicitGC來禁用顯式GC勉痴。并且你需要知道,調(diào)用System.gc()并不能夠保證full gc馬上就能被執(zhí)行树肃。
所以在后面打代碼中蒸矛,會(huì)進(jìn)行最多9次嘗試,看是否有足夠的可用堆外內(nèi)存來分配堆外內(nèi)存。并且每次嘗試之前雏掠,都對(duì)延遲等待時(shí)間斩祭,已給JVM足夠的時(shí)間去完成full gc操作。如果9次嘗試后依舊沒有足夠的可用堆外內(nèi)存來分配本次堆外內(nèi)存乡话,則拋出OutOfMemoryError("Direct buffer memory”)異常摧玫。
注意,這里之所以用使用full gc的很重要的一個(gè)原因是:System.gc()會(huì)對(duì)新生代的老生代都會(huì)進(jìn)行內(nèi)存回收蚊伞,這樣會(huì)比較徹底地回收DirectByteBuffer對(duì)象以及他們關(guān)聯(lián)的堆外內(nèi)存.
DirectByteBuffer對(duì)象本身其實(shí)是很小的席赂,但是它后面可能關(guān)聯(lián)了一個(gè)非常大的堆外內(nèi)存,因此我們通常稱之為冰山對(duì)象.
我們做ygc的時(shí)候會(huì)將新生代里的不可達(dá)的DirectByteBuffer對(duì)象及其堆外內(nèi)存回收了时迫,但是無法對(duì)old里的DirectByteBuffer對(duì)象及其堆外內(nèi)存進(jìn)行回收,這也是我們通常碰到的最大的問題掠拳。( 并且堆外內(nèi)存多用于生命期中等或較長(zhǎng)的對(duì)象 )
如果有大量的DirectByteBuffer對(duì)象移到了old,但是又一直沒有做cms gc或者full gc溺欧,而只進(jìn)行ygc,那么我們的物理內(nèi)存可能被慢慢耗光姐刁,但是我們還不知道發(fā)生了什么,因?yàn)閔eap明明剩余的內(nèi)存還很多(前提是我們禁用了System.gc – JVM參數(shù)DisableExplicitGC)聂使。
總的來說,Bits.reserveMemory(size, cap)方法在可用堆外內(nèi)存不足以分配給當(dāng)前要?jiǎng)?chuàng)建的堆外內(nèi)存大小時(shí)柏靶,會(huì)實(shí)現(xiàn)以下的步驟來嘗試完成本次堆外內(nèi)存的創(chuàng)建:
① 觸發(fā)一次非堵塞的Reference#tryHandlePending(false)弃理。該方法會(huì)將已經(jīng)被JVM垃圾回收的DirectBuffer對(duì)象的堆外內(nèi)存釋放。
② 如果進(jìn)行一次堆外內(nèi)存資源回收后屎蜓,還不夠進(jìn)行本次堆外內(nèi)存分配的話痘昌,則進(jìn)行 System.gc()。System.gc()會(huì)觸發(fā)一個(gè)full gc炬转,但你需要知道辆苔,調(diào)用System.gc()并不能夠保證full gc馬上就能被執(zhí)行。所以在后面打代碼中扼劈,會(huì)進(jìn)行最多9次嘗試驻啤,看是否有足夠的可用堆外內(nèi)存來分配堆外內(nèi)存。并且每次嘗試之前测僵,都對(duì)延遲等待時(shí)間街佑,已給JVM足夠的時(shí)間去完成full gc操作。
注意捍靠,如果你設(shè)置了-XX:+DisableExplicitGC沐旨,將會(huì)禁用顯示GC,這會(huì)使System.gc()調(diào)用無效榨婆。
③ 如果9次嘗試后依舊沒有足夠的可用堆外內(nèi)存來分配本次堆外內(nèi)存磁携,則拋出OutOfMemoryError("Direct buffer memory”)異常。
那么可用堆外內(nèi)存到底是多少了良风?谊迄,即默認(rèn)堆外存內(nèi)存有多大:
① 如果我們沒有通過-XX:MaxDirectMemorySize來指定最大的堆外內(nèi)存。則??
② 如果我們沒通過-Dsun.nio.MaxDirectMemorySize指定了這個(gè)屬性烟央,且它不等于-1统诺。則??
③ 那么最大堆外內(nèi)存的值來自于directMemory = Runtime.getRuntime().maxMemory(),這是一個(gè)native方法
JNIEXPORT jlong JNICALL
Java_java_lang_Runtime_maxMemory(JNIEnv *env, jobject this)
{
return JVM_MaxMemory();
}
JVM_ENTRY_NO_ENV(jlong, JVM_MaxMemory(void))
JVMWrapper("JVM_MaxMemory");
size_t n = Universe::heap()->max_capacity();
return convert_size_t_to_jlong(n);
JVM_END
其中在我們使用CMS GC的情況下也就是我們?cè)O(shè)置的-Xmx的值里除去一個(gè)survivor的大小就是默認(rèn)的堆外內(nèi)存的大小了疑俭。
堆外內(nèi)存回收
Cleaner是PhantomReference的子類粮呢,并通過自身的next和prev字段維護(hù)的一個(gè)雙向鏈表。PhantomReference的作用在于跟蹤垃圾回收過程钞艇,并不會(huì)對(duì)對(duì)象的垃圾回收過程造成任何的影響啄寡。
所以cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 用于對(duì)當(dāng)前構(gòu)造的DirectByteBuffer對(duì)象的垃圾回收過程進(jìn)行跟蹤。
當(dāng)DirectByteBuffer對(duì)象從pending狀態(tài) ——> enqueue狀態(tài)時(shí)哩照,會(huì)觸發(fā)Cleaner的clean()挺物,而Cleaner的clean()的方法會(huì)實(shí)現(xiàn)通過unsafe對(duì)堆外內(nèi)存的釋放。
??雖然Cleaner不會(huì)調(diào)用到Reference.clear()飘弧,但Cleaner的clean()方法調(diào)用了remove(this),即將當(dāng)前Cleaner從Cleaner鏈表中移除蹋岩,這樣當(dāng)clean()執(zhí)行完后学少,Cleaner就是一個(gè)無引用指向的對(duì)象了,也就是可被GC回收的對(duì)象扣囊。
thunk方法:
通過配置參數(shù)的方式來回收堆外內(nèi)存
同時(shí)我們可以通過-XX:MaxDirectMemorySize來指定最大的堆外內(nèi)存大小侵歇,當(dāng)使用達(dá)到了閾值的時(shí)候?qū)⒄{(diào)用System.gc()來做一次full gc吓蘑,以此來回收掉沒有被使用的堆外內(nèi)存坟冲。
堆外內(nèi)存那些事
使用堆外內(nèi)存的原因
- 對(duì)垃圾回收停頓的改善
因?yàn)閒ull gc 意味著徹底回收溃蔫,徹底回收時(shí),垃圾收集器會(huì)對(duì)所有分配的堆內(nèi)內(nèi)存進(jìn)行完整的掃描私痹,這意味著一個(gè)重要的事實(shí)——這樣一次垃圾收集對(duì)Java應(yīng)用造成的影響统刮,跟堆的大小是成正比的。過大的堆會(huì)影響Java應(yīng)用的性能侥蒙。如果使用堆外內(nèi)存的話,堆外內(nèi)存是直接受操作系統(tǒng)管理( 而不是虛擬機(jī) )桦山。這樣做的結(jié)果就是能保持一個(gè)較小的堆內(nèi)內(nèi)存醋旦,以減少垃圾收集對(duì)應(yīng)用的影響。 - 在某些場(chǎng)景下可以提升程序I/O操縱的性能钉凌。少去了將數(shù)據(jù)從堆內(nèi)內(nèi)存拷貝到堆外內(nèi)存的步驟捂人。
什么情況下使用堆外內(nèi)存
- 堆外內(nèi)存適用于生命周期中等或較長(zhǎng)的對(duì)象。( 如果是生命周期較短的對(duì)象酸纲,在YGC的時(shí)候就被回收了瑟匆,就不存在大內(nèi)存且生命周期較長(zhǎng)的對(duì)象在FGC對(duì)應(yīng)用造成的性能影響 )。
- 直接的文件拷貝操作疾嗅,或者I/O操作代承。直接使用堆外內(nèi)存就能少去內(nèi)存從用戶內(nèi)存拷貝到系統(tǒng)內(nèi)存的操作渐扮,因?yàn)镮/O操作是系統(tǒng)內(nèi)核內(nèi)存和設(shè)備間的通信掖棉,而不是通過程序直接和外設(shè)通信的啊片。
- 同時(shí)玖像,還可以使用 池+堆外內(nèi)存 的組合方式捐寥,來對(duì)生命周期較短祖驱,但涉及到I/O操作的對(duì)象進(jìn)行堆外內(nèi)存的再使用。( Netty中就使用了該方式 )
堆外內(nèi)存 VS 內(nèi)存池
- 內(nèi)存池:主要用于兩類對(duì)象:①生命周期較短捺僻,且結(jié)構(gòu)簡(jiǎn)單的對(duì)象匕坯,在內(nèi)存池中重復(fù)利用這些對(duì)象能增加CPU緩存的命中率,從而提高性能锹雏;②加載含有大量重復(fù)對(duì)象的大片數(shù)據(jù),此時(shí)使用內(nèi)存池能減少垃圾回收的時(shí)間礁遵。
- 堆外內(nèi)存:它和內(nèi)存池一樣采记,也能縮短垃圾回收時(shí)間,但是它適用的對(duì)象和內(nèi)存池完全相反兼砖。內(nèi)存池往往適用于生命期較短的可變對(duì)象既棺,而生命期中等或較長(zhǎng)的對(duì)象,正是堆外內(nèi)存要解決的戏挡。
堆外內(nèi)存的特點(diǎn)
- 對(duì)于大內(nèi)存有良好的伸縮性
- 對(duì)垃圾回收停頓的改善可以明顯感覺到
- 在進(jìn)程間可以共享褐墅,減少虛擬機(jī)間的復(fù)制
堆外內(nèi)存的一些問題
- 堆外內(nèi)存回收問題,以及堆外內(nèi)存的泄漏問題妥凳。這個(gè)在上面的源碼解析已經(jīng)提到了
- 堆外內(nèi)存的數(shù)據(jù)結(jié)構(gòu)問題:堆外內(nèi)存最大的問題就是你的數(shù)據(jù)結(jié)構(gòu)變得不那么直觀,如果數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜屑那,就要對(duì)它進(jìn)行串行化(serialization)艘款,而串行化本身也會(huì)影響性能哗咆。另一個(gè)問題是由于你可以使用更大的內(nèi)存,你可能開始擔(dān)心虛擬內(nèi)存(即硬盤)的速度對(duì)你的影響了姥份。
參考
http://lovestblog.cn/blog/2015/05/12/direct-buffer/
http://www.infoq.com/cn/news/2014/12/external-memory-heap-memory
http://www.reibang.com/p/85e931636f27
圣思園《并發(fā)與Netty》課程