简单介绍
他是一个通道的出站缓冲区,所有要写的数据都会先存在这里,等到要刷新的时候才会真的写出去。
内部类Entry
其实消息都是封装成内部的Entry类的,存储结构是一个单链表。我来看看这个类,其实他是一个对象池,可以复用:
static final class Entry {
//Entry对象池
private static final ObjectPool<Entry> RECYCLER = ObjectPool.newPool(new ObjectCreator<Entry>() {
@Override
public Entry newObject(Handle<Entry> handle) {
return new Entry(handle);
}
});
private final Handle<Entry> handle;//池化操作的处理器
Entry next;//链表的下一个
Object msg;//信息
ByteBuffer[] bufs;//缓存字节缓冲区数组,为了复用提高效率
ByteBuffer buf;//缓存字节缓冲区,为了复用提高效率
ChannelPromise promise;//回调
long progress;//当前进度,即已经传了多少数据
long total;//总共的数据大小
int pendingSize;//待冲刷的评估大小,要加上96
int count = -1;
boolean cancelled;//是否被取消了
private Entry(Handle<Entry> handle) {
this.handle = handle;
}
//用对象池的方式创建实体
static Entry newInstance(Object msg, int size, long total, ChannelPromise promise) {
Entry entry = RECYCLER.get();//从池子里获取
entry.msg = msg;//消息
entry.pendingSize = size + CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD;//评估的大小
entry.total = total;//具体大小
entry.promise = promise;
return entry;
}
//取消了,返回待冲刷的评估大小
int cancel() {
if (!cancelled) {
cancelled = true;//取消标识
int pSize = pendingSize;
// release message and replace with an empty buffer
ReferenceCountUtil.safeRelease(msg);//释放
msg = Unpooled.EMPTY_BUFFER;
pendingSize = 0;
total = 0;
progress = 0;
bufs = null;
buf = null;
return pSize;
}
return 0;
}
//用完初始化后再放回收到池子里
void recycle() {
next = null;//设置成null
bufs = null;
buf = null;
msg = null;
promise = null;
progress = 0;
total = 0;
pendingSize = 0;
count = -1;
cancelled = false;
handle.recycle(this);
}
//回收当前实体并获取下一个实体,为什么要先获取下一个再回收呢,因为回收的时候把next设置null啦
Entry recycleAndGetNext() {
Entry next = this.next;
recycle();
return next;
}
}
重要属性
static final int CHANNEL_OUTBOUND_BUFFER_ENTRY_OVERHEAD =//出站实体的额外开销96字节
SystemPropertyUtil.getInt("io.netty.transport.outboundBufferEntrySizeOverhead", 96);
//保存线程对应的缓冲区,默认是1024个ByteBuffer数组,FastThreadLocal比一般的ThreadLocal要快,他是利用数组,内部用的是常量索引的数组,不是hash算法
private static final FastThreadLocal<ByteBuffer[]> NIO_BUFFERS = new FastThreadLocal<ByteBuffer[]>() {
@Override
protected ByteBuffer[] initialValue() throws Exception {
return new ByteBuffer[1024];
}
};
// 单链表结构
// The Entry that is the first in the linked-list structure that was flushed
private Entry flushedEntry;//第一个要冲刷的实体
// The Entry which is the first unflushed in the linked-list structure
private Entry unflushedEntry;//第一个未冲刷的实体
// The Entry which represents the tail of the buffer
private Entry tailEntry;//尾结点实体
// The number of flushed entries that are not written yet
private int flushed;//要冲刷的数量,但是还没真正冲刷出去,就是出站缓冲区大小
private int nioBufferCount;//可以冲刷的缓冲区个数
private long nioBufferSize;//可以写出的总的缓冲区数组数据大小
private boolean inFail;//是否冲刷失败
//原子操作totalPendingSize
private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =
AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
@SuppressWarnings("UnusedDeclaration")
private volatile long totalPendingSize;//待冲刷缓冲区的字节总数
//原子操作unwritable
private static final AtomicIntegerFieldUpdater<ChannelOutboundBuffer> UNWRITABLE_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "unwritable");
@SuppressWarnings("UnusedDeclaration")
private volatile int unwritable;
private volatile Runnable fireChannelWritabilityChangedTask;//写能力改变的任务
decrementPendingOutboundBytes
减少待出站的字节数,默认是提交任务延迟触发的:
void decrementPendingOutboundBytes(long size) {
decrementPendingOutboundBytes(size, true, true);
}
private void decrementPendingOutboundBytes(long size, boolean invokeLater, boolean notifyWritability) {
if (size == 0) {
return;
}
//总数减少
long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, -size);
if (notifyWritability && newWriteBufferSize < channel.config().getWriteBufferLowWaterMark()) {//如果小于阈值就说明可写了,提交触发通道的任务
setWritable(invokeLater);
}
}
setWritable
原子操作修改成可写状态:
private void setWritable(boolean invokeLater) {
for (;;) {
final int oldValue = unwritable;
final int newValue = oldValue & ~1;
if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
if (oldValue != 0 && newValue == 0) {//可写的值由非0到0,能写了
fireChannelWritabilityChanged(invokeLater);//能写了就要触发事件了
}
break;
}
}
}
fireChannelWritabilityChanged
根据传入的参数判断是否要立即触发还时提交任务:
private void fireChannelWritabilityChanged(boolean invokeLater) {
final ChannelPipeline pipeline = channel.pipeline();
if (invokeLater) {
Runnable task = fireChannelWritabilityChangedTask;
if (task == null) {
fireChannelWritabilityChangedTask = task = new Runnable() {
@Override
public void run() {
pipeline.fireChannelWritabilityChanged();
}
};
}
channel.eventLoop().execute(task);
} else {
pipeline.fireChannelWritabilityChanged();
}
}
total
获取要写出去消息的真正字节大小:
//获取大小
private static long total(Object msg) {
if (msg instanceof ByteBuf) {
return ((ByteBuf) msg).readableBytes();
}
if (msg instanceof FileRegion) {
return ((FileRegion) msg).count();
}
if (msg instanceof ByteBufHolder) {
return ((ByteBufHolder) msg).content().readableBytes();
}
return -1;
}
这个就是真实的大小,其实和默认的评估大小类似,只是FileRegion
类型不一样,这个具体后面会说:
current
获取当前冲刷的实体消息:
public Object current() {
Entry entry = flushedEntry;
if (entry == null) {
return null;
}
return entry.msg;
}
currentProgress
获取当前实体冲刷的进度:
public long currentProgress() {
Entry entry = flushedEntry;
if (entry == null) {
return 0;
}
return entry.progress;
}
progress
通知当前实体冲刷的进度:
public void progress(long amount) {
Entry e = flushedEntry;
assert e != null;
ChannelPromise p = e.promise;
long progress = e.progress + amount;
e.progress = progress;
if (p instanceof ChannelProgressivePromise) {
((ChannelProgressivePromise) p).tryProgress(progress, e.total);
}
}
size
这个flushed
就是出站缓冲区大小:
public int size() {
return flushed;
}
public boolean isEmpty() {
return flushed == 0;
}
clearNioBuffers
清除缓存区数组中的数据,都设置为null
:
private void clearNioBuffers() {
int count = nioBufferCount;
if (count > 0) {
nioBufferCount = 0;
Arrays.fill(NIO_BUFFERS.get(), 0, count, null);
}
}
removeEntry
从单链表中删除实体:
//链表中删除实体
private void removeEntry(Entry e) {
if (-- flushed == 0) {//最后一个了
// processed everything
flushedEntry = null;
if (e == tailEntry) {
tailEntry = null;
unflushedEntry = null;
}
} else {
flushedEntry = e.next;//跳过e,指向下一个
}
}
remove
从当前已经标记为冲刷的实体:
public boolean remove() {
Entry e = flushedEntry;//删除当前消息
if (e == null) {//没有要冲刷的了,就清除缓存
clearNioBuffers();
return false;
}
Object msg = e.msg;
ChannelPromise promise = e.promise;
int size = e.pendingSize;
removeEntry(e);//从链表中删除实体
if (!e.cancelled) {//没取消就要释放消息
// only release message, notify and decrement if it was not canceled before.
ReferenceCountUtil.safeRelease(msg);//释放消息缓存区
safeSuccess(promise);//设置回调成功
decrementPendingOutboundBytes(size, false, true);//减少待冲刷缓冲区大小,立即触发相应事件
}
// recycle the entry
e.recycle();//回收实体
return true;
}
remove(Throwable cause)
失败后删除:
public boolean remove(Throwable cause) {
return remove0(cause, true);
}
//失败后删除,要抛出异常,立即触发事件,成功删除一个实体返回true,没有实体删除了就清楚缓存,返回false
private boolean remove0(Throwable cause, boolean notifyWritability) {
Entry e = flushedEntry;
if (e == null) {//删完为止
clearNioBuffers();
return false;
}
Object msg = e.msg;
ChannelPromise promise = e.promise;
int size = e.pendingSize;
removeEntry(e);
if (!e.cancelled) {
// only release message, fail and decrement if it was not canceled before.
ReferenceCountUtil.safeRelease(msg);
safeFail(promise, cause);//回调失败,把异常传递进去
decrementPendingOutboundBytes(size, false, notifyWritability);//减少待冲刷缓冲区大小,立即触发相应事件
}
// recycle the entry
e.recycle();
return true;
}
removeBytes(long writtenBytes)
删除所有已经冲刷出去的字节数据,假设缓存区中数据都是ByteBuf
类型的,其实这个就是当数据全部冲刷出去之后,要把缓存清空:
public void removeBytes(long writtenBytes) {
for (;;) {
Object msg = current();//获取当前要冲刷的实体数据
if (!(msg instanceof ByteBuf)) {//没有实体或者类型不是ByteBuf的直接跳出循环
assert writtenBytes == 0;
break;
}
final ByteBuf buf = (ByteBuf) msg;
final int readerIndex = buf.readerIndex();//读索引
final int readableBytes = buf.writerIndex() - readerIndex;//可读的数据大小
if (readableBytes <= writtenBytes) {//可读的数据小于等于要写的数据大小
if (writtenBytes != 0) {
progress(readableBytes);//刷新写进度
writtenBytes -= readableBytes;//处理完了就减去
}
remove();//删除当前的实体
} else { // readableBytes > writtenBytes
if (writtenBytes != 0) {
buf.readerIndex(readerIndex + (int) writtenBytes);//设置读索引
progress(writtenBytes);//刷新写进度
}
break;
}
}
clearNioBuffers();//清楚缓存
}
nioBuffers
获取直接数组缓冲区,就是要冲刷的时候用的:
public ByteBuffer[] nioBuffers() {
return nioBuffers(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
nioBuffers(int maxCount, long maxBytes)
这个方法会将缓冲区数组数据限制在传入参数中,其实内部就是获取每一个实体中的消息ByteBuf
,将他们放入缓冲区数组,统计有多少可读的缓冲区和总的数据大小:
public ByteBuffer[] nioBuffers(int maxCount, long maxBytes) {
assert maxCount > 0;//最大缓冲区数量
assert maxBytes > 0;//最大字节数
long nioBufferSize = 0;//缓冲区字节数
int nioBufferCount = 0;//缓冲区数量
final InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
ByteBuffer[] nioBuffers = NIO_BUFFERS.get(threadLocalMap);//快速获取当前线程对应的缓冲区数组
Entry entry = flushedEntry;//获得要冲刷的实体
while (isFlushedEntry(entry) && entry.msg instanceof ByteBuf) {//有冲刷标记的,且实体消息是ByteBuf类型
if (!entry.cancelled) {//没有取消的
ByteBuf buf = (ByteBuf) entry.msg;//获得消息
final int readerIndex = buf.readerIndex();//读索引
final int readableBytes = buf.writerIndex() - readerIndex;//可读字节
if (readableBytes > 0) {
if (maxBytes - readableBytes < nioBufferSize && nioBufferCount != 0) {//超过最大字节数且缓冲区数量不为0
// If the nioBufferSize + readableBytes will overflow maxBytes, and there is at least one entry
// we stop populate the ByteBuffer array. This is done for 2 reasons:
// 1. bsd/osx don't allow to write more bytes then Integer.MAX_VALUE with one writev(...) call
// and so will return 'EINVAL', which will raise an IOException. On Linux it may work depending
// on the architecture and kernel but to be safe we also enforce the limit here.
// 2. There is no sense in putting more data in the array than is likely to be accepted by the
// OS.
// 底层不允许写的字节数大于Integer.MAX_VALUE,否则会报错。
// See also:
// - https://www.freebsd.org/cgi/man.cgi?query=write&sektion=2
// - http://linux.die.net/man/2/writev
break;
}
nioBufferSize += readableBytes;//累加字节数
int count = entry.count;//数量,默认是-1
if (count == -1) {
//noinspection ConstantValueVariableUse
entry.count = count = buf.nioBufferCount();//默认返回是1个缓冲区,也可能是其他,那可能就是entry的bufs
}
int neededSpace = min(maxCount, nioBufferCount + count);//需要的空间,取最小
if (neededSpace > nioBuffers.length) {//如果大于1024就进行扩张
nioBuffers = expandNioBufferArray(nioBuffers, neededSpace, nioBufferCount);
NIO_BUFFERS.set(threadLocalMap, nioBuffers);
}
if (count == 1) {
ByteBuffer nioBuf = entry.buf;//默认buf是空的
if (nioBuf == null) {
// 缓存一个缓冲区,实体可以复用,到时候就不用创建了
entry.buf = nioBuf = buf.internalNioBuffer(readerIndex, readableBytes);
}
nioBuffers[nioBufferCount++] = nioBuf;//用实体的缓冲区,添加到缓存区数组里
} else {//为了内联,又封装了一个方法来获得缓冲区个数,不过这个条件分支不会经常进来
//用实体的缓冲区数组
nioBufferCount = nioBuffers(entry, buf, nioBuffers, nioBufferCount, maxCount);
}
if (nioBufferCount == maxCount) {//达到上限就跳出循环
break;
}
}
}
entry = entry.next;
}
this.nioBufferCount = nioBufferCount;//缓冲区个数
this.nioBufferSize = nioBufferSize;//缓冲区总数据大小
return nioBuffers;
}
expandNioBufferArray
进行缓冲区数组的扩张,每次是原来的2
倍大小:
private static ByteBuffer[] expandNioBufferArray(ByteBuffer[] array, int neededSpace, int size) {
int newCapacity = array.length;
do {
// double capacity until it is big enough
// See https://github.com/netty/netty/issues/1890
newCapacity <<= 1;//每次扩张为原来的2倍
if (newCapacity < 0) {
throw new IllegalStateException();
}
} while (neededSpace > newCapacity);
ByteBuffer[] newArray = new ByteBuffer[newCapacity];
System.arraycopy(array, 0, newArray, 0, size);//复制原来的数据到新数组中
return newArray;
}
nioBuffers(Entry entry, ByteBuf buf, ByteBuffer[] nioBuffers, int nioBufferCount, int maxCount)
这里是用实体的缓冲区数组,统计可以读的缓冲区个数,以及将实体缓冲区数组中的可读的缓冲区放入缓冲区数组中:
private static int nioBuffers(Entry entry, ByteBuf buf, ByteBuffer[] nioBuffers, int nioBufferCount, int maxCount) {
ByteBuffer[] nioBufs = entry.bufs;//默认都是空
if (nioBufs == null) {
// cached ByteBuffers as they may be expensive to create in terms
// of Object allocation //缓存一份,因为创建成本高,而且这个实体是池化的,所以要复用
entry.bufs = nioBufs = buf.nioBuffers();
}
//遍历实体的缓冲区数组
for (int i = 0; i < nioBufs.length && nioBufferCount < maxCount; ++i) {
ByteBuffer nioBuf = nioBufs[i];
if (nioBuf == null) {//获得空了就说明后面就没有了,跳出循环即可
break;
} else if (!nioBuf.hasRemaining()) {//缓冲区不可读了,就继续下一个
continue;
}
nioBuffers[nioBufferCount++] = nioBuf;//将可读的缓冲区添加到缓冲区数组,然后nioBufferCount+1
}
return nioBufferCount;
}
所以这里可能 缓冲区个数nioBufferCount
可能是会比实体的消息个数多 。
用户定义写标志
这个我就不多说了,就是用户自定来定义哪一位代表可不可写入,第1
到31
位,默认是第0
位的:
/**
* Returns {@code true} if and only if {@linkplain #totalPendingWriteBytes() the total number of pending bytes} did
* not exceed the write watermark of the {@link Channel} and
* no {@linkplain #setUserDefinedWritability(int, boolean) user-defined writability flag} has been set to
* {@code false}.
*/
public boolean isWritable() {
return unwritable == 0;
}
/** 用户定义标志位代表可不可写
* Returns {@code true} if and only if the user-defined writability flag at the specified index is set to
* {@code true}.
*/
public boolean getUserDefinedWritability(int index) {
return (unwritable & writabilityMask(index)) == 0;
}
/**
* Sets a user-defined writability flag at the specified index.
*/
public void setUserDefinedWritability(int index, boolean writable) {
if (writable) {
setUserDefinedWritability(index);
} else {
clearUserDefinedWritability(index);
}
}
private void setUserDefinedWritability(int index) {
final int mask = ~writabilityMask(index);
for (;;) {
final int oldValue = unwritable;
final int newValue = oldValue & mask;
if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
if (oldValue != 0 && newValue == 0) {
fireChannelWritabilityChanged(true);
}
break;
}
}
}
private void clearUserDefinedWritability(int index) {
final int mask = writabilityMask(index);
for (;;) {
final int oldValue = unwritable;
final int newValue = oldValue | mask;
if (UNWRITABLE_UPDATER.compareAndSet(this, oldValue, newValue)) {
if (oldValue == 0 && newValue != 0) {
fireChannelWritabilityChanged(true);
}
break;
}
}
}
private static int writabilityMask(int index) {
if (index < 1 || index > 31) {
throw new IllegalArgumentException("index: " + index + " (expected: 1~31)");
}
return 1 << index;
}
failFlushed
冲刷失败了就将实体全部删除:
void failFlushed(Throwable cause, boolean notify) {
// Make sure that this method does not reenter. A listener added to the current promise can be notified by the
// current thread in the tryFailure() call of the loop below, and the listener can trigger another fail() call
// indirectly (usually by closing the channel.)
//
// See https://github.com/netty/netty/issues/1501
if (inFail) {//失败就返回了
return;
}
try {
inFail = true;
for (;;) {
//这里就是前面的失败就删除,默认删除一个会返回true,循环继续,继续删除下一个,直到全部删除位置才返回false,才跳出循环
if (!remove0(cause, notify)) {
break;
}
}
} finally {
inFail = false;
}
}
bytesBeforeUnwritable
不可写之前还有多少字节可以写:
public long bytesBeforeUnwritable() {
long bytes = channel.config().getWriteBufferHighWaterMark() - totalPendingSize;
// If bytes is negative we know we are not writable, but if bytes is non-negative we have to check writability.
// Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
// together. totalPendingSize will be updated before isWritable().
if (bytes > 0) {
return isWritable() ? bytes : 0;
}
return 0;
}
bytesBeforeWritable
可写之前还有多少个字节在等待排队要写的:
public long bytesBeforeWritable() {
long bytes = totalPendingSize - channel.config().getWriteBufferLowWaterMark();
// If bytes is negative we know we are writable, but if bytes is non-negative we have to check writability.
// Note that totalPendingSize and isWritable() use different volatile variables that are not synchronized
// together. totalPendingSize will be updated before isWritable().
if (bytes > 0) {
return isWritable() ? 0 : bytes;
}
return 0;
}
forEachFlushedMessage
刷新每一个消息冲刷的进度:
public void forEachFlushedMessage(MessageProcessor processor) throws Exception {
ObjectUtil.checkNotNull(processor, "processor");
Entry entry = flushedEntry;
if (entry == null) {
return;
}
do {
if (!entry.cancelled) {
if (!processor.processMessage(entry.msg)) {
return;
}
}
entry = entry.next;
} while (isFlushedEntry(entry));//是否标记了冲刷的
}
//有冲刷标志的
private boolean isFlushedEntry(Entry e) {
return e != null && e != unflushedEntry;
}
至此方法都讲完了,其实这个就是一个出站的数据缓存的地方,你来一个数据,我就给你封装成一个实体,然后最后要写出去之前给你打好标记,然后装进我的缓存区数组里,让通道写出,最后再删除写完实体,清空缓存区数组。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。