堆外內(nèi)存 之 DirectByteBuffer 詳解

堆外內(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》課程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市澈歉,隨后出現(xiàn)的幾起案子屿衅,更是在濱河造成了極大的恐慌,老刑警劉巖凯砍,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悟衩,死亡現(xiàn)場(chǎng)離奇詭異栓拜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挑势,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門潮饱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诫给,“玉大人啦扬,你說我怎么就攤上這事扑毡∈⑾眨” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵换帜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我挺邀,道長(zhǎng),這世上最難降的妖魔是什么泣矛? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任您朽,我火速辦了婚禮换淆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讯屈。我一直安慰自己涮母,他們只是感情好躁愿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布彤钟。 她就那樣靜靜地躺著,像睡著了一般吠勘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剧防,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天峭拘,我揣著相機(jī)與錄音,去河邊找鬼鸡挠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛彭沼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姓惑,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼于毙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼辅搬!你這毒婦竟也來了堪遂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤甘耿,失蹤者是張志新(化名)和其女友劉穎佳恬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毁葱,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倾剿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年前痘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芹缔。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡最欠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚜点,到底是詐尸還是另有隱情拌阴,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布陪拘,位于F島的核電站藻丢,受9級(jí)特大地震影響剪撬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜馍佑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一拭荤、第九天 我趴在偏房一處隱蔽的房頂上張望疫诽。 院中可真熱鬧,春花似錦雏亚、人聲如沸摩钙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽功舀。三九已至,卻和暖如春遣铝,著一層夾襖步出監(jiān)牢的瞬間莉擒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工填硕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扁眯,地道東北人翅帜。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓涝滴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親歼疮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容