Netty系列之Direct Buffers

1碉京、什么是堆外內(nèi)存
堆外內(nèi)存是相對于堆內(nèi)內(nèi)存的一個概念僵芹。堆內(nèi)內(nèi)存是由JVM所管控的Java進(jìn)程內(nèi)存瀑志,我們平時在Java中創(chuàng)建的對象都處于堆內(nèi)內(nèi)存中,并且它們遵循JVM的內(nèi)存管理機(jī)制脖阵,JVM會采用垃圾回收機(jī)制統(tǒng)一管理它們的內(nèi)存皂股。

堆外內(nèi)存使用Native函數(shù)庫(通過Unsafe類的allocateMemory()方法申請分配內(nèi)存,底層會調(diào)用操作系統(tǒng)的的malloc函數(shù))直接分配(native堆)命黔,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作呜呐。

2、堆外內(nèi)存解決了什么問題
解決HeapByteBuffer存在的問題:
如果os和jvm都是用jvm里邊的數(shù)據(jù)區(qū)域悍募, 但是jvm會對這塊內(nèi)存區(qū)域進(jìn)行GC回收蘑辑,可能會對這塊內(nèi)存的數(shù)據(jù)進(jìn)行更改,根據(jù)我們的假設(shè)坠宴,由于這塊區(qū)域os也在使用以躯,jvm對這塊共享數(shù)據(jù)發(fā)生了變更,os那邊就會出現(xiàn)數(shù)據(jù)錯亂的情況。那么如果不讓jvm對這塊共享區(qū)域進(jìn)行GC是不是可以避免這個問題呢忧设?答案是不行的,也會存在問題颠通,如果jvm不對其進(jìn)行GC回收址晕,jvm這邊可能會出現(xiàn)OOM的內(nèi)存溢出。因此只能拷貝jvm的那一份到os的內(nèi)存空間顿锰,即使jvm那邊的數(shù)據(jù)區(qū)域被改變谨垃,但是os里邊的不會受到影響,等os使用io結(jié)束后會對這塊區(qū)域進(jìn)行回收硼控,因?yàn)檫@是os的管理范圍之內(nèi)刘陶。這樣就造成性能降低。
因此牢撼,在JDK1.4中新加入了NIO匙隔,引入了一種基于通道(Channel)和緩存區(qū)(Buffer)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存(native堆)熏版,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作纷责。這樣能在一些場景中顯著提高性能,因?yàn)楸苊饬嗽贘ava堆和Native堆中來回復(fù)制數(shù)據(jù)撼短。

3再膳、堆外內(nèi)存的實(shí)現(xiàn)
DirectByteBuffer 對象引用位于 Java 內(nèi)存模型的堆里面,JVM 可以對 DirectByteBuffer 的對象進(jìn)行內(nèi)存分配和回收管理曲横,一般使用 DirectByteBuffer 的靜態(tài)方法 allocateDirect() 創(chuàng)建 DirectByteBuffer 實(shí)例并分配內(nèi)存喂柒。

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

DirectByteBuffer 內(nèi)部的字節(jié)緩沖區(qū)位在于堆外的(用戶態(tài))直接內(nèi)存,它是通過 Unsafe 的本地方法 allocateMemory() 進(jìn)行內(nèi)存分配禾嫉,底層調(diào)用的是操作系統(tǒng)的 malloc() 函數(shù)灾杰。

DirectByteBuffer(int cap) {
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

除此之外,初始化 DirectByteBuffer 時還會創(chuàng)建一個 Deallocator 線程夭织,并通過 Cleaner 的 freeMemory() 方法來對直接內(nèi)存進(jìn)行回收操作吭露,freeMemory() 底層調(diào)用的是操作系統(tǒng)的 free() 函數(shù)。

private static class Deallocator implements Runnable {
    private static Unsafe unsafe = Unsafe.getUnsafe();

    private long address;
    private long size;
    private int capacity;

    private Deallocator(long address, long size, int capacity) {
        assert (address != 0);
        this.address = address;
        this.size = size;
        this.capacity = capacity;
    }

    public void run() {
        if (address == 0) {
            return;
        }
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }
}

由于使用 DirectByteBuffer 分配的是系統(tǒng)本地的內(nèi)存尊惰,不在 JVM 的管控范圍之內(nèi)讲竿,因此直接內(nèi)存的回收和堆內(nèi)存的回收不同,直接內(nèi)存如果使用不當(dāng)弄屡,很容易造成 OutOfMemoryError题禀。

DirectByteBuffer 和零拷貝有什么關(guān)系?

DirectByteBuffer 是 MappedByteBuffer 的具體實(shí)現(xiàn)類膀捷。實(shí)際上迈嘹,Util.newMappedByteBuffer() 方法通過反射機(jī)制獲取 DirectByteBuffer 的構(gòu)造器,然后創(chuàng)建一個 DirectByteBuffer 的實(shí)例,對應(yīng)的是一個單獨(dú)用于內(nèi)存映射的構(gòu)造方法:

protected DirectByteBuffer(int cap, long addr, FileDescriptor fd, Runnable unmapper) {
    super(-1, 0, cap, cap, fd);
    address = addr;
    cleaner = Cleaner.create(this, unmapper);
    att = null;
}

在 MappedByteBuffer 進(jìn)行內(nèi)存映射時秀仲,它的 map() 方法會通過 Util.newMappedByteBuffer() 來創(chuàng)建一個緩沖區(qū)實(shí)例融痛,初始化的代碼如下:

static MappedByteBuffer newMappedByteBuffer(int size, long addr, FileDescriptor fd,
                                            Runnable unmapper) {
    MappedByteBuffer dbb;
    if (directByteBufferConstructor == null)
        initDBBConstructor();
    try {
        dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance(
            new Object[] { new Integer(size), new Long(addr), fd, unmapper });
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
        throw new InternalError(e);
    }
    return dbb;
}

private static void initDBBRConstructor() {
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            try {
                Class<?> cl = Class.forName("java.nio.DirectByteBufferR");
                Constructor<?> ctor = cl.getDeclaredConstructor(
                    new Class<?>[] { int.class, long.class, FileDescriptor.class,
                                    Runnable.class });
                ctor.setAccessible(true);
                directByteBufferRConstructor = ctor;
            } catch (ClassNotFoundException | NoSuchMethodException |
                     IllegalArgumentException | ClassCastException x) {
                throw new InternalError(x);
            }
            return null;
        }});
}

因此,除了允許分配操作系統(tǒng)的直接內(nèi)存以外神僵,DirectByteBuffer 本身也具有文件內(nèi)存映射的功能雁刷。我們需要關(guān)注的是,DirectByteBuffer 在 MappedByteBuffer 的基礎(chǔ)上提供了內(nèi)存映像文件的隨機(jī)讀取 get() 和寫入 write() 的操作保礼。

內(nèi)存映像文件的隨機(jī)讀操作

public byte get() {
    return ((unsafe.getByte(ix(nextGetIndex()))));
}

public byte get(int i) {
    return ((unsafe.getByte(ix(checkIndex(i)))));
}

內(nèi)存映像文件的隨機(jī)寫操作

public ByteBuffer put(byte x) {
    unsafe.putByte(ix(nextPutIndex()), ((x)));
    return this;
}

public ByteBuffer put(int i, byte x) {
    unsafe.putByte(ix(checkIndex(i)), ((x)));
    return this;
}

內(nèi)存映像文件的隨機(jī)讀寫都是借助 ix() 方法實(shí)現(xiàn)定位的沛励, ix() 方法通過內(nèi)存映射空間的內(nèi)存首地址(address)和給定偏移量 i 計(jì)算出指針地址,然后由 unsafe 類的 get() 和 put() 方法和對指針指向的數(shù)據(jù)進(jìn)行讀取或?qū)懭搿?/p>

private long ix(int i) {
    return address + ((long)i << 0);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末炮障,一起剝皮案震驚了整個濱河市目派,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胁赢,老刑警劉巖企蹭,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肛真,死亡現(xiàn)場離奇詭異塑娇,居然都是意外死亡鲤嫡,警方通過查閱死者的電腦和手機(jī)仲器,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門瞬痘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兔跌,“玉大人沃于,你說我怎么就攤上這事菱涤∷剑” “怎么了螺男?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纵穿。 經(jīng)常有香客問我下隧,道長,這世上最難降的妖魔是什么谓媒? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任淆院,我火速辦了婚禮,結(jié)果婚禮上句惯,老公的妹妹穿的比我還像新娘土辩。我一直安慰自己,他們只是感情好抢野,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布拷淘。 她就那樣靜靜地躺著,像睡著了一般指孤。 火紅的嫁衣襯著肌膚如雪启涯。 梳的紋絲不亂的頭發(fā)上贬堵,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音结洼,去河邊找鬼黎做。 笑死,一個胖子當(dāng)著我的面吹牛补君,可吹牛的內(nèi)容都是我干的引几。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挽铁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了敞掘?” 一聲冷哼從身側(cè)響起叽掘,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玖雁,沒想到半個月后更扁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赫冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年浓镜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劲厌。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡膛薛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出补鼻,到底是詐尸還是另有隱情哄啄,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布风范,位于F島的核電站咨跌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏硼婿。R本人自食惡果不足惜锌半,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寇漫。 院中可真熱鬧刊殉,春花似錦、人聲如沸猪腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陋葡。三九已至亚亲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捌归。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工肛响, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惜索。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓特笋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巾兆。 傳聞我的和親對象是個殘疾皇子猎物,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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