分析Netty直接內(nèi)存原理及應(yīng)用
一、通常的內(nèi)存模型概述
一般地,系統(tǒng)為了保證系統(tǒng)本身的安全性和健壯性,會將內(nèi)存從邏輯上隔離成內(nèi)核區(qū)域和用戶區(qū)域,這很容易理解。因為用戶行為不可控性太強,暴露得太多,就容易導(dǎo)致各種神奇的用法,超出系統(tǒng)的控制范圍。當(dāng)然,有的語言是支持直接控制內(nèi)存的,比如C, 你可以用一個指針,訪問內(nèi)存中的幾乎任意位置的數(shù)據(jù)(除了一些硬件地址)。而像匯編,則可以訪問任意地址。而這些底層的語言,已經(jīng)離我們越來越遠了,它基本上和普通程序員關(guān)系不大了。
用戶很多時候的編程控制,都是在用戶區(qū)域進行的,比如我做一些加減乘除,如 Integer a = 2; Integer b = 3; Integer c = a * b; 這種操作, 所有操作就是在用戶空間上完成的。這些操作,不會有內(nèi)核區(qū)域的介入。但是有些操作,則必須由內(nèi)核進行,比如對文件的讀寫,就是不同設(shè)備之間的數(shù)據(jù)交換,也就是io類操作。這類操作因為有非常的難度實現(xiàn),所以一定是由操作系統(tǒng)來完成底層的操作的。那么,第一手的數(shù)據(jù)必定要經(jīng)過內(nèi)核區(qū)域。然而我們的代碼是跑在用戶區(qū)的,那么,通常情況下,就會存在內(nèi)核區(qū)數(shù)據(jù),拷貝到用戶區(qū)數(shù)據(jù)的這么一個過程。這是一個讀的過程,而寫的過程則是一個相反的操作,從用戶區(qū)拷貝數(shù)據(jù)到內(nèi)核區(qū),然后再由內(nèi)核完成io操作。
直接將內(nèi)存劃分為內(nèi)核區(qū)與用戶區(qū),實在是太泛了,不能說錯,但有一種說了等于沒說的感覺。
所以,對內(nèi)存的劃分,還需要再細點,即所謂的內(nèi)存模型或者內(nèi)存區(qū)域。各語言各場景各實現(xiàn)自然是百家爭鳴,無可厚非。但大致就是按照一定的規(guī)則,切分成不同用途的區(qū)域,然后在需要的時候向該區(qū)域進行內(nèi)存分配,并保存到相應(yīng)的表或者標(biāo)識中,以便后續(xù)可讀或不可再分配。而這其中,還有個非常重要的點是,除了知道如何分配內(nèi)存之外,還要知道如何回收內(nèi)存。另外,如何保證內(nèi)存的可見性,也是一個內(nèi)存模型需要考慮的重要話題。
具體實現(xiàn)就不用說了,因為沒有一個放之四海而皆準(zhǔn)的說法,我也沒那能耐講清楚這事情。大家自行腦補吧。
二、Java中的直接內(nèi)存原理
首先,來說說為什么java中會有直接內(nèi)存這個概念?我們知道,java中有很重要的一個內(nèi)存區(qū)域,即堆內(nèi)存,幾乎所有的對象都堆上進行分配,所以,大部分的GC工作,也是針對堆進行的。關(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的管控呢?因為jvm管控的是用戶空間,而有的場景則必須要內(nèi)核空間的介入,整個過程才能完成。而如果用戶空間想要獲取數(shù)據(jù),則必須要像內(nèi)核中請求復(fù)制數(shù)據(jù),數(shù)據(jù)才對用戶空間可見。而很多這種場景,復(fù)制數(shù)據(jù)的目的,僅僅是為了使用一次其數(shù)據(jù),做了相應(yīng)的轉(zhuǎn)換后,就不再使用有關(guān)系,比如流數(shù)據(jù)的接入過程。這個復(fù)制的過程,則必定有不少的性能損耗,所以就有直接內(nèi)存的出現(xiàn)。它的目的在于避免內(nèi)核空間和用戶空間之間進行無意義的數(shù)據(jù)復(fù)制,從而提升程序性能。
直接內(nèi)存不受jvm管控,那么它受誰的管控呢?實際上,是由操作系統(tǒng)的底層進行管控的,在進行內(nèi)存分配請求時,系統(tǒng)會申請一段共享區(qū)域。由內(nèi)核和用戶代碼共享這里的數(shù)據(jù)寫入,即內(nèi)核寫入的數(shù)據(jù),用戶代碼可以直接訪問,用戶代碼寫入的數(shù)據(jù),內(nèi)核可以直接使用。在底層,是由mmap這種函數(shù)接口來實現(xiàn)的共享內(nèi)存的。
而在java層面,則是使用DirectByteBuffer來呈現(xiàn)的,它的創(chuàng)建、使用、刪除如下:
// 創(chuàng)建直接內(nèi)存空間實例
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作為一個高性能網(wǎng)絡(luò)通信框架,重要的工作就是在處理網(wǎng)絡(luò)io問題。那么,在它的場景里,使用上直接內(nèi)存這一大殺器,則是再好不過了。那么,netty是如何利用它的呢?
兩個場景:1. 向應(yīng)用傳遞網(wǎng)絡(luò)數(shù)據(jù)時(讀過程); 2. 應(yīng)用向遠端傳遞數(shù)據(jù)時(寫過程);
// 寫過程,將msg轉(zhuǎn)換為直接內(nèi)存存儲的二進制數(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;
// 默認 preferDirect = true;
buf = allocateBuffer(ctx, cast, preferDirect);
try {
// 調(diào)用子類的實現(xiàn),編碼數(shù)據(jù),以便實現(xiàn)私有協(xié)議
encode(ctx, cast, buf);
} finally {
ReferenceCountUtil.release(cast);
}
if (buf.isReadable()) {
// 寫數(shù)據(jù)到遠端
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() 將會得到一個內(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的本地方法,進行內(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)進行內(nèi)存數(shù)據(jù)的寫入的。
最后,來看下它如何將寫數(shù)據(jù)到遠端:
// 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ā)送到遠端了。
最后,我們來看下簡要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, 此處實際就是直接內(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ù)時,仍然使用直接內(nèi)存進行數(shù)據(jù)接收,從而達到內(nèi)核與用戶共享,無需拷貝的目的。
以上,就是netty對整個直接內(nèi)存的操作方式了??雌饋碛悬c復(fù)雜,主要netty到處都是其設(shè)計哲學(xué)的體現(xiàn),無論是一個寫事件、讀事件、或者是狀態(tài)變更事件,都是一長串的流水線操作。當(dāng)然了,我們此處討論的是,其如何使用直接內(nèi)存的。它通過使用一個 PooledUnsafeDirectByteBuf , 最終引用jdk的 direct = ByteBuffer.allocateDirect(1); 使用 DirectByteBuffer 實現(xiàn)直接內(nèi)存的使用。并使用其構(gòu)造方法 DirectByteBuffer(long addr, int cap) 進行直接內(nèi)存對象創(chuàng)建。
四、總結(jié)
從整體上來說,直接內(nèi)存減少了進行io時的內(nèi)存復(fù)制操,但其僅為內(nèi)核與用戶空間的內(nèi)存復(fù)制,因為用戶空間的數(shù)據(jù)復(fù)制是并不可少的,因為最終它們都必須要轉(zhuǎn)換為二進制流,才能被不同空間的程序讀取。但創(chuàng)建直接內(nèi)存對象的開銷要高于創(chuàng)建普通內(nèi)存對象,因為它可能需要維護更復(fù)雜的關(guān)系環(huán)境。事實上,直接內(nèi)存可以做到不同進程間的內(nèi)存共享,而這在普通對象內(nèi)存中是無法做到的(不過java是單進程的,不care此場景)。java的直接內(nèi)存的使用,僅為使用系統(tǒng)提供的一個便捷接口,適應(yīng)更好的場景。
直接內(nèi)存實際上也可以叫共享內(nèi)存,它可以實現(xiàn)不同進程之間的通信,即不同進程可以看到其他進程對本塊內(nèi)存地址的修改。這是一種高效的進程間通信方式,這對于多進程應(yīng)用很有幫助。但對于多線程應(yīng)用則不是必須,因為多線程本身就是共享內(nèi)存的。而類似于nginx之類的應(yīng)用,則非常有用了。因為對于一些全局計數(shù)器,必然需要多進程維護,通過共享內(nèi)存完美解決。
而netty作為一個網(wǎng)絡(luò)通信框架,則是為了更好處理具體場景,更合理的使用了直接內(nèi)存,從而成就了所謂的零拷貝,高性能的基石之一。所以,一個好的框架,一定是解決某類問題的翹楚,它不一定是功能開創(chuàng)者,但一定是很好的繼承者。
另外,內(nèi)存管理是個非常復(fù)雜的問題。 但又很重要,值得我們花大量時間去研究。
以上就是分析Netty直接內(nèi)存原理及應(yīng)用的詳細內(nèi)容,更多關(guān)于Netty 直接內(nèi)存原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
jasypt 集成SpringBoot 數(shù)據(jù)庫密碼加密操作
這篇文章主要介紹了jasypt 集成SpringBoot 數(shù)據(jù)庫密碼加密操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
Lombok中@EqualsAndHashCode注解的使用及說明
這篇文章主要介紹了Lombok中@EqualsAndHashCode注解的使用及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
Elasticsearch查詢之Match Query示例詳解
這篇文章主要為大家介紹了Elasticsearch查詢之Match查詢示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
SpringMVC通過模型視圖ModelAndView渲染視圖的實現(xiàn)
這篇文章主要介紹了SpringMVC通過模型視圖ModelAndView渲染視圖的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
java sleep()和wait()的區(qū)別點總結(jié)
在本篇文章里小編給大家整理了一篇關(guān)于java sleep()和wait()的區(qū)別的相關(guān)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2021-04-04

