分析Netty直接內(nèi)存原理及應(yīng)用
一、通常的內(nèi)存模型概述
一般地,系統(tǒng)為了保證系統(tǒng)本身的安全性和健壯性,會將內(nèi)存從邏輯上隔離成內(nèi)核區(qū)域和用戶區(qū)域,這很容易理解。因?yàn)橛脩粜袨椴豢煽匦蕴珡?qiáng),暴露得太多,就容易導(dǎo)致各種神奇的用法,超出系統(tǒng)的控制范圍。當(dāng)然,有的語言是支持直接控制內(nèi)存的,比如C, 你可以用一個(gè)指針,訪問內(nèi)存中的幾乎任意位置的數(shù)據(jù)(除了一些硬件地址)。而像匯編,則可以訪問任意地址。而這些底層的語言,已經(jīng)離我們越來越遠(yuǎn)了,它基本上和普通程序員關(guān)系不大了。
用戶很多時(shí)候的編程控制,都是在用戶區(qū)域進(jìn)行的,比如我做一些加減乘除,如 Integer a = 2; Integer b = 3; Integer c = a * b; 這種操作, 所有操作就是在用戶空間上完成的。這些操作,不會有內(nèi)核區(qū)域的介入。但是有些操作,則必須由內(nèi)核進(jìn)行,比如對文件的讀寫,就是不同設(shè)備之間的數(shù)據(jù)交換,也就是io類操作。這類操作因?yàn)橛蟹浅5碾y度實(shí)現(xiàn),所以一定是由操作系統(tǒng)來完成底層的操作的。那么,第一手的數(shù)據(jù)必定要經(jīng)過內(nèi)核區(qū)域。然而我們的代碼是跑在用戶區(qū)的,那么,通常情況下,就會存在內(nèi)核區(qū)數(shù)據(jù),拷貝到用戶區(qū)數(shù)據(jù)的這么一個(gè)過程。這是一個(gè)讀的過程,而寫的過程則是一個(gè)相反的操作,從用戶區(qū)拷貝數(shù)據(jù)到內(nèi)核區(qū),然后再由內(nèi)核完成io操作。
直接將內(nèi)存劃分為內(nèi)核區(qū)與用戶區(qū),實(shí)在是太泛了,不能說錯(cuò),但有一種說了等于沒說的感覺。
所以,對內(nèi)存的劃分,還需要再細(xì)點(diǎn),即所謂的內(nèi)存模型或者內(nèi)存區(qū)域。各語言各場景各實(shí)現(xiàn)自然是百家爭鳴,無可厚非。但大致就是按照一定的規(guī)則,切分成不同用途的區(qū)域,然后在需要的時(shí)候向該區(qū)域進(jìn)行內(nèi)存分配,并保存到相應(yīng)的表或者標(biāo)識中,以便后續(xù)可讀或不可再分配。而這其中,還有個(gè)非常重要的點(diǎn)是,除了知道如何分配內(nèi)存之外,還要知道如何回收內(nèi)存。另外,如何保證內(nèi)存的可見性,也是一個(gè)內(nèi)存模型需要考慮的重要話題。
具體實(shí)現(xiàn)就不用說了,因?yàn)闆]有一個(gè)放之四海而皆準(zhǔn)的說法,我也沒那能耐講清楚這事情。大家自行腦補(bǔ)吧。
二、Java中的直接內(nèi)存原理
首先,來說說為什么java中會有直接內(nèi)存這個(gè)概念?我們知道,java中有很重要的一個(gè)內(nèi)存區(qū)域,即堆內(nèi)存,幾乎所有的對象都堆上進(jìn)行分配,所以,大部分的GC工作,也是針對堆進(jìn)行的。關(guān)聯(lián)上一節(jié)所講的事,堆內(nèi)存我們可以劃分到用戶空間內(nèi)存區(qū)域去。應(yīng)該說,java只要將這一塊內(nèi)存管理好了,基本上就可以管理好java的對象的生命周期了。那么,到底什么直接內(nèi)存?和堆內(nèi)存又有啥關(guān)系?
直接內(nèi)存是脫離掉堆空間的,它不屬于java的堆,其他區(qū)域也不屬于,即直接內(nèi)存不受jvm管控。它屬于受系統(tǒng)直接控制的一段內(nèi)存區(qū)域。
為什么直接內(nèi)存要脫離jvm的管控呢?因?yàn)閖vm管控的是用戶空間,而有的場景則必須要內(nèi)核空間的介入,整個(gè)過程才能完成。而如果用戶空間想要獲取數(shù)據(jù),則必須要像內(nèi)核中請求復(fù)制數(shù)據(jù),數(shù)據(jù)才對用戶空間可見。而很多這種場景,復(fù)制數(shù)據(jù)的目的,僅僅是為了使用一次其數(shù)據(jù),做了相應(yīng)的轉(zhuǎn)換后,就不再使用有關(guān)系,比如流數(shù)據(jù)的接入過程。這個(gè)復(fù)制的過程,則必定有不少的性能損耗,所以就有直接內(nèi)存的出現(xiàn)。它的目的在于避免內(nèi)核空間和用戶空間之間進(jìn)行無意義的數(shù)據(jù)復(fù)制,從而提升程序性能。
直接內(nèi)存不受jvm管控,那么它受誰的管控呢?實(shí)際上,是由操作系統(tǒng)的底層進(jìn)行管控的,在進(jìn)行內(nèi)存分配請求時(shí),系統(tǒng)會申請一段共享區(qū)域。由內(nèi)核和用戶代碼共享這里的數(shù)據(jù)寫入,即內(nèi)核寫入的數(shù)據(jù),用戶代碼可以直接訪問,用戶代碼寫入的數(shù)據(jù),內(nèi)核可以直接使用。在底層,是由mmap這種函數(shù)接口來實(shí)現(xiàn)的共享內(nèi)存的。
而在java層面,則是使用DirectByteBuffer來呈現(xiàn)的,它的創(chuàng)建、使用、刪除如下:
// 創(chuàng)建直接內(nèi)存空間實(shí)例 ByteBuffer buffer = ByteBuffer.allocateDirect(1600); for (int i = 0; i < 90_0000; i++) { for (int j = 0; j < 199; j++) { // 數(shù)據(jù)的寫入 buffer.putInt(j); } buffer.flip(); for (int j = 0; j < 199; j++) { // 數(shù)據(jù)的讀取 buffer.get(); } // 數(shù)據(jù)清理 buffer.clear(); }
三、Netty中使用直接內(nèi)存
知道了直接內(nèi)存的使用過程,那么如何找到更好的場景,則是需要我們?nèi)グl(fā)現(xiàn)的。netty作為一個(gè)高性能網(wǎng)絡(luò)通信框架,重要的工作就是在處理網(wǎng)絡(luò)io問題。那么,在它的場景里,使用上直接內(nèi)存這一大殺器,則是再好不過了。那么,netty是如何利用它的呢?
兩個(gè)場景:1. 向應(yīng)用傳遞網(wǎng)絡(luò)數(shù)據(jù)時(shí)(讀過程); 2. 應(yīng)用向遠(yuǎn)端傳遞數(shù)據(jù)時(shí)(寫過程);
// 寫過程,將msg轉(zhuǎn)換為直接內(nèi)存存儲的二進(jìn)制數(shù)據(jù) // io.netty.handler.codec.MessageToByteEncoder#write @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { ByteBuf buf = null; try { if (acceptOutboundMessage(msg)) { @SuppressWarnings("unchecked") I cast = (I) msg; // 默認(rèn) preferDirect = true; buf = allocateBuffer(ctx, cast, preferDirect); try { // 調(diào)用子類的實(shí)現(xiàn),編碼數(shù)據(jù),以便實(shí)現(xiàn)私有協(xié)議 encode(ctx, cast, buf); } finally { ReferenceCountUtil.release(cast); } if (buf.isReadable()) { // 寫數(shù)據(jù)到遠(yuǎn)端 ctx.write(buf, promise); } else { buf.release(); ctx.write(Unpooled.EMPTY_BUFFER, promise); } buf = null; } else { ctx.write(msg, promise); } } catch (EncoderException e) { throw e; } catch (Throwable e) { throw new EncoderException(e); } finally { if (buf != null) { buf.release(); } } } // io.netty.handler.codec.MessageToByteEncoder#allocateBuffer /** * Allocate a {@link ByteBuf} which will be used as argument of {@link #encode(ChannelHandlerContext, I, ByteBuf)}. * Sub-classes may override this method to return {@link ByteBuf} with a perfect matching {@code initialCapacity}. */ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg, boolean preferDirect) throws Exception { if (preferDirect) { // PooledByteBufAllocator return ctx.alloc().ioBuffer(); } else { return ctx.alloc().heapBuffer(); } } // io.netty.buffer.AbstractByteBufAllocator#ioBuffer() @Override public ByteBuf ioBuffer() { if (PlatformDependent.hasUnsafe()) { return directBuffer(DEFAULT_INITIAL_CAPACITY); } return heapBuffer(DEFAULT_INITIAL_CAPACITY); } // io.netty.buffer.AbstractByteBufAllocator#directBuffer(int) @Override public ByteBuf directBuffer(int initialCapacity) { return directBuffer(initialCapacity, DEFAULT_MAX_CAPACITY); } @Override public ByteBuf directBuffer(int initialCapacity, int maxCapacity) { if (initialCapacity == 0 && maxCapacity == 0) { return emptyBuf; } validate(initialCapacity, maxCapacity); return newDirectBuffer(initialCapacity, maxCapacity); } // io.netty.buffer.PooledByteBufAllocator#newDirectBuffer @Override protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { PoolThreadCache cache = threadCache.get(); PoolArena<ByteBuffer> directArena = cache.directArena; final ByteBuf buf; if (directArena != null) { buf = directArena.allocate(cache, initialCapacity, maxCapacity); } else { buf = PlatformDependent.hasUnsafe() ? UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) : new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity); } return toLeakAwareBuffer(buf); } // io.netty.buffer.PoolArena#allocate(io.netty.buffer.PoolThreadCache, int, int) PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) { PooledByteBuf<T> buf = newByteBuf(maxCapacity); allocate(cache, buf, reqCapacity); return buf; } // io.netty.buffer.PoolArena.DirectArena#newByteBuf @Override protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) { if (HAS_UNSAFE) { return PooledUnsafeDirectByteBuf.newInstance(maxCapacity); } else { return PooledDirectByteBuf.newInstance(maxCapacity); } } private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) { final int normCapacity = normalizeCapacity(reqCapacity); if (isTinyOrSmall(normCapacity)) { // capacity < pageSize int tableIdx; PoolSubpage<T>[] table; boolean tiny = isTiny(normCapacity); if (tiny) { // < 512 if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } tableIdx = tinyIdx(normCapacity); table = tinySubpagePools; } else { if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } tableIdx = smallIdx(normCapacity); table = smallSubpagePools; } final PoolSubpage<T> head = table[tableIdx]; /** * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and * {@link PoolChunk#free(long)} may modify the doubly linked list as well. */ synchronized (head) { final PoolSubpage<T> s = head.next; if (s != head) { assert s.doNotDestroy && s.elemSize == normCapacity; long handle = s.allocate(); assert handle >= 0; s.chunk.initBufWithSubpage(buf, handle, reqCapacity); incTinySmallAllocation(tiny); return; } } synchronized (this) { allocateNormal(buf, reqCapacity, normCapacity); } incTinySmallAllocation(tiny); return; } if (normCapacity <= chunkSize) { if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { // was able to allocate out of the cache so move on return; } synchronized (this) { allocateNormal(buf, reqCapacity, normCapacity); ++allocationsNormal; } } else { // Huge allocations are never served via the cache so just call allocateHuge allocateHuge(buf, reqCapacity); } } // io.netty.util.internal.PlatformDependent0#newDirectBuffer static ByteBuffer newDirectBuffer(long address, int capacity) { ObjectUtil.checkPositiveOrZero(capacity, "capacity"); try { return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity); } catch (Throwable cause) { // Not expected to ever throw! if (cause instanceof Error) { throw (Error) cause; } throw new Error(cause); } }
向ByteBuffer中寫入數(shù)據(jù)過程, 即是向直接內(nèi)存中寫入數(shù)據(jù)的過程,它可能不像普通的堆對象一樣簡單咯。
// io.netty.buffer.AbstractByteBuf#writeBytes(byte[]) @Override public ByteBuf writeBytes(byte[] src) { writeBytes(src, 0, src.length); return this; } @Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { ensureWritable(length); setBytes(writerIndex, src, srcIndex, length); writerIndex += length; return this; } // io.netty.buffer.PooledUnsafeDirectByteBuf#setBytes(int, byte[], int, int) @Override public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) { // addr() 將會得到一個(gè)內(nèi)存地址 UnsafeByteBufUtil.setBytes(this, addr(index), index, src, srcIndex, length); return this; } // io.netty.buffer.PooledUnsafeDirectByteBuf#addr private long addr(int index) { return memoryAddress + index; } // io.netty.buffer.UnsafeByteBufUtil#setBytes(io.netty.buffer.AbstractByteBuf, long, int, byte[], int, int) static void setBytes(AbstractByteBuf buf, long addr, int index, byte[] src, int srcIndex, int length) { buf.checkIndex(index, length); if (length != 0) { // 將字節(jié)數(shù)據(jù)copy到DirectByteBuffer中 PlatformDependent.copyMemory(src, srcIndex, addr, length); } } // io.netty.util.internal.PlatformDependent#copyMemory(byte[], int, long, long) public static void copyMemory(byte[] src, int srcIndex, long dstAddr, long length) { PlatformDependent0.copyMemory(src, BYTE_ARRAY_BASE_OFFSET + srcIndex, null, dstAddr, length); } // io.netty.util.internal.PlatformDependent0#copyMemory(java.lang.Object, long, java.lang.Object, long, long) static void copyMemory(Object src, long srcOffset, Object dst, long dstOffset, long length) { //UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, length); while (length > 0) { long size = Math.min(length, UNSAFE_COPY_THRESHOLD); // 最終由jvm的本地方法,進(jìn)行內(nèi)存的copy, 此處dst為null, 即數(shù)據(jù)只會copy到對應(yīng)的 dstOffset 中 // 偏移基數(shù)就是: 各種基礎(chǔ)地址 ARRAY_OBJECT_BASE_OFFSET... UNSAFE.copyMemory(src, srcOffset, dst, dstOffset, size); length -= size; srcOffset += size; dstOffset += size; } }
可以看到,最后直接內(nèi)存的寫入,是通過 Unsafe 類,對操作系統(tǒng)進(jìn)行內(nèi)存數(shù)據(jù)的寫入的。
最后,來看下它如何將寫數(shù)據(jù)到遠(yuǎn)端:
// io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, io.netty.channel.ChannelPromise) @Override public ChannelFuture write(final Object msg, final ChannelPromise promise) { if (msg == null) { throw new NullPointerException("msg"); } try { if (isNotValidPromise(promise, true)) { ReferenceCountUtil.release(msg); // cancelled return promise; } } catch (RuntimeException e) { ReferenceCountUtil.release(msg); throw e; } write(msg, false, promise); return promise; } private void write(Object msg, boolean flush, ChannelPromise promise) { AbstractChannelHandlerContext next = findContextOutbound(); final Object m = pipeline.touch(msg, next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { if (flush) { next.invokeWriteAndFlush(m, promise); } else { next.invokeWrite(m, promise); } } else { AbstractWriteTask task; if (flush) { task = WriteAndFlushTask.newInstance(next, m, promise); } else { task = WriteTask.newInstance(next, m, promise); } safeExecute(executor, task, promise, m); } } private void invokeWrite(Object msg, ChannelPromise promise) { if (invokeHandler()) { invokeWrite0(msg, promise); } else { write(msg, promise); } } private void invokeWrite0(Object msg, ChannelPromise promise) { try { ((ChannelOutboundHandler) handler()).write(this, msg, promise); } catch (Throwable t) { notifyOutboundHandlerException(t, promise); } } // io.netty.channel.DefaultChannelPipeline.HeadContext#write @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { unsafe.write(msg, promise); } // io.netty.channel.AbstractChannel.AbstractUnsafe#write @Override public final void write(Object msg, ChannelPromise promise) { assertEventLoop(); ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; if (outboundBuffer == null) { // If the outboundBuffer is null we know the channel was closed and so // need to fail the future right away. If it is not null the handling of the rest // will be done in flush0() // See https://github.com/netty/netty/issues/2362 safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION); // release message now to prevent resource-leak ReferenceCountUtil.release(msg); return; } int size; try { // 轉(zhuǎn)換msg為直接內(nèi)存,如有必要 msg = filterOutboundMessage(msg); size = pipeline.estimatorHandle().size(msg); if (size < 0) { size = 0; } } catch (Throwable t) { safeSetFailure(promise, t); ReferenceCountUtil.release(msg); return; } // 將msg放入outboundBuffer中,即相當(dāng)于寫完了數(shù)據(jù) outboundBuffer.addMessage(msg, size, promise); } // io.netty.channel.nio.AbstractNioByteChannel#filterOutboundMessage @Override protected final Object filterOutboundMessage(Object msg) { if (msg instanceof ByteBuf) { ByteBuf buf = (ByteBuf) msg; if (buf.isDirect()) { return msg; } return newDirectBuffer(buf); } if (msg instanceof FileRegion) { return msg; } throw new UnsupportedOperationException( "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES); } // io.netty.channel.ChannelOutboundBuffer#addMessage /** * Add given message to this {@link ChannelOutboundBuffer}. The given {@link ChannelPromise} will be notified once * the message was written. */ public void addMessage(Object msg, int size, ChannelPromise promise) { Entry entry = Entry.newInstance(msg, size, total(msg), promise); if (tailEntry == null) { flushedEntry = null; } else { Entry tail = tailEntry; tail.next = entry; } tailEntry = entry; if (unflushedEntry == null) { unflushedEntry = entry; } // increment pending bytes after adding message to the unflushed arrays. // See https://github.com/netty/netty/issues/1619 // 如有必要,立即觸發(fā) fireChannelWritabilityChanged 事件,從而使立即向網(wǎng)絡(luò)寫入數(shù)據(jù) incrementPendingOutboundBytes(entry.pendingSize, false); }
大概就是說,通過直接內(nèi)存寫好的數(shù)據(jù),只需要再調(diào)用下內(nèi)核的接入接口,將直接內(nèi)存的數(shù)據(jù)放入緩沖,就可以被發(fā)送到遠(yuǎn)端了。
最后,我們來看下簡要netty對于網(wǎng)絡(luò)數(shù)據(jù)的接入讀取過程,以辨別是否使用了直接內(nèi)存,以及是如何使用的。
// io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read @Override public final void read() { final ChannelConfig config = config(); final ChannelPipeline pipeline = pipeline(); final ByteBufAllocator allocator = config.getAllocator(); final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); allocHandle.reset(config); ByteBuf byteBuf = null; boolean close = false; try { do { // 分配創(chuàng)建ByteBuffer, 此處實(shí)際就是直接內(nèi)存的體現(xiàn) byteBuf = allocHandle.allocate(allocator); // 將數(shù)據(jù)讀取到ByteBuffer中 allocHandle.lastBytesRead(doReadBytes(byteBuf)); if (allocHandle.lastBytesRead() <= 0) { // nothing was read. release the buffer. byteBuf.release(); byteBuf = null; close = allocHandle.lastBytesRead() < 0; break; } allocHandle.incMessagesRead(1); readPending = false; // 讀取到一部分?jǐn)?shù)據(jù),就向pipeline的下游傳遞,而非全部完成后再傳遞 pipeline.fireChannelRead(byteBuf); byteBuf = null; } while (allocHandle.continueReading()); allocHandle.readComplete(); pipeline.fireChannelReadComplete(); if (close) { closeOnRead(pipeline); } } catch (Throwable t) { handleReadException(pipeline, byteBuf, t, close, allocHandle); } finally { // Check if there is a readPending which was not processed yet. // This could be for two reasons: // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method // // See https://github.com/netty/netty/issues/2254 if (!readPending && !config.isAutoRead()) { removeReadOp(); } } } } // io.netty.channel.DefaultMaxMessagesRecvByteBufAllocator.MaxMessageHandle#allocate @Override public ByteBuf allocate(ByteBufAllocator alloc) { return alloc.ioBuffer(guess()); } // io.netty.buffer.AbstractByteBufAllocator#ioBuffer(int) @Override public ByteBuf ioBuffer(int initialCapacity) { if (PlatformDependent.hasUnsafe()) { return directBuffer(initialCapacity); } return heapBuffer(initialCapacity); }
可見同樣,在接入數(shù)據(jù)時(shí),仍然使用直接內(nèi)存進(jìn)行數(shù)據(jù)接收,從而達(dá)到內(nèi)核與用戶共享,無需拷貝的目的。
以上,就是netty對整個(gè)直接內(nèi)存的操作方式了??雌饋碛悬c(diǎn)復(fù)雜,主要netty到處都是其設(shè)計(jì)哲學(xué)的體現(xiàn),無論是一個(gè)寫事件、讀事件、或者是狀態(tài)變更事件,都是一長串的流水線操作。當(dāng)然了,我們此處討論的是,其如何使用直接內(nèi)存的。它通過使用一個(gè) PooledUnsafeDirectByteBuf , 最終引用jdk的 direct = ByteBuffer.allocateDirect(1); 使用 DirectByteBuffer 實(shí)現(xiàn)直接內(nèi)存的使用。并使用其構(gòu)造方法 DirectByteBuffer(long addr, int cap) 進(jìn)行直接內(nèi)存對象創(chuàng)建。
四、總結(jié)
從整體上來說,直接內(nèi)存減少了進(jìn)行io時(shí)的內(nèi)存復(fù)制操,但其僅為內(nèi)核與用戶空間的內(nèi)存復(fù)制,因?yàn)橛脩艨臻g的數(shù)據(jù)復(fù)制是并不可少的,因?yàn)樽罱K它們都必須要轉(zhuǎn)換為二進(jìn)制流,才能被不同空間的程序讀取。但創(chuàng)建直接內(nèi)存對象的開銷要高于創(chuàng)建普通內(nèi)存對象,因?yàn)樗赡苄枰S護(hù)更復(fù)雜的關(guān)系環(huán)境。事實(shí)上,直接內(nèi)存可以做到不同進(jìn)程間的內(nèi)存共享,而這在普通對象內(nèi)存中是無法做到的(不過java是單進(jìn)程的,不care此場景)。java的直接內(nèi)存的使用,僅為使用系統(tǒng)提供的一個(gè)便捷接口,適應(yīng)更好的場景。
直接內(nèi)存實(shí)際上也可以叫共享內(nèi)存,它可以實(shí)現(xiàn)不同進(jìn)程之間的通信,即不同進(jìn)程可以看到其他進(jìn)程對本塊內(nèi)存地址的修改。這是一種高效的進(jìn)程間通信方式,這對于多進(jìn)程應(yīng)用很有幫助。但對于多線程應(yīng)用則不是必須,因?yàn)槎嗑€程本身就是共享內(nèi)存的。而類似于nginx之類的應(yīng)用,則非常有用了。因?yàn)閷τ谝恍┤钟?jì)數(shù)器,必然需要多進(jìn)程維護(hù),通過共享內(nèi)存完美解決。
而netty作為一個(gè)網(wǎng)絡(luò)通信框架,則是為了更好處理具體場景,更合理的使用了直接內(nèi)存,從而成就了所謂的零拷貝,高性能的基石之一。所以,一個(gè)好的框架,一定是解決某類問題的翹楚,它不一定是功能開創(chuàng)者,但一定是很好的繼承者。
另外,內(nèi)存管理是個(gè)非常復(fù)雜的問題。 但又很重要,值得我們花大量時(shí)間去研究。
以上就是分析Netty直接內(nèi)存原理及應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于Netty 直接內(nèi)存原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用java + OpenCV破解頂象面積驗(yàn)證碼的示例
這篇文章主要介紹了使用java + OpenCV破解頂象面積驗(yàn)證碼的示例,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-0215道非常經(jīng)典的Java面試題 附詳細(xì)答案
這篇文章主要為大家推薦了15道非常經(jīng)典的Java面試題,附詳細(xì)答案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10jasypt 集成SpringBoot 數(shù)據(jù)庫密碼加密操作
這篇文章主要介紹了jasypt 集成SpringBoot 數(shù)據(jù)庫密碼加密操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11Lombok中@EqualsAndHashCode注解的使用及說明
這篇文章主要介紹了Lombok中@EqualsAndHashCode注解的使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Elasticsearch查詢之Match Query示例詳解
這篇文章主要為大家介紹了Elasticsearch查詢之Match查詢示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04SpringMVC通過模型視圖ModelAndView渲染視圖的實(shí)現(xiàn)
這篇文章主要介紹了SpringMVC通過模型視圖ModelAndView渲染視圖的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12java sleep()和wait()的區(qū)別點(diǎn)總結(jié)
在本篇文章里小編給大家整理了一篇關(guān)于java sleep()和wait()的區(qū)別的相關(guān)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2021-04-04