本文开始,将主要围绕netty相关知识展开,力求从宏观上把握整个内存结构。
ByteBuf
为了解决NIO 中 ByteBuffer
使用不当问题,例如读模式写模式不能同时进行,无法自动扩容等。Netty 新建了一种字节容器ByteBuf
。
在 ByteBuf
中,定义有 readerIndex
,writerIndex
和 capacity
。
内部组织结构如下:
即可读区域、可写,以及容量。
对于 ByteBuf
更好的理解是一个接口(虽然是抽象类),因为里面大部分方法都是abstract。
AbstractByteBuf
AbstractByteBuf
作为 ByteBuf
子类,同样是一个抽象类,里面定义了 以下字段:
int readerIndex; // 读index
int writerIndex; // 写index
private int markedReaderIndex; // 标记 读index
private int markedWriterIndex; // 标记 写 index
private int maxCapacity; // 最大容量
AbstractByteBuf
中并没有定义具体的字节容器,而是针对上面几个字节容器的index进行操作,具体操作字节容器,则是抽象方法交给子类实现(因为有池化非池化,以及是否直接内存区别)。例如setBytes
@Override
public ByteBuf setBytes(int index, ByteBuf src, int length) {
checkIndex(index, length);
if (src == null) {
throw new NullPointerException("src");
}
if (checkBounds) {
checkReadableBounds(src, length);
}
setBytes(index, src, src.readerIndex(), length);
src.readerIndex(src.readerIndex() + length);
return this;
}
对于 setBytes
方法:
则是作为抽象方法,由子类实现:
public abstract ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length);
AbstractReferenceCountedByteBuf
再往下看,再看一个子类为 AbstractReferenceCountedByteBuf
。里面维持这个一个int类型的refCnt
,即维持一个当前 ByteBuf
被引用次数。
ReferenceCounted
引用计数接口方法:
int refCnt()
返回当前引用数ReferenceCounted retain()
增加引用数ReferenceCounted touch()
记录当前位置boolean release();
减少引用数
Unpooled
往后,ByteBuf
又能分为Pooled和Unpooled,即池化和非池化内存,池化和非池化后面文章专门分析。
具体ByteBuf
又分为堆内存Heap
和 直接内存 Direct
。
UnpooledHeapByteBuf
看看其内部成员:
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
private final ByteBufAllocator alloc;
private byte[] array; // netty byteBuf操作数
private ByteBuffer tmpNioBuf; // 对接java nio 的bytebuffer
...
}
具体看的有点懵,从writeByte(int value)
看看处理逻辑:
AbstractByteBuf
的 writeByte
:
@Override
public ByteBuf writeByte(int value) {
ensureWritable(1); // 保证大小,看是否需要扩容
setByte(writerIndex++, value); // writerIndex 自增
return this;
}
AbstractByteBuf
的 setByte(int index, int value)
:
@Override
public ByteBuf setByte(int index, int value) {
checkIndex(index); // 检查index
_setByte(index, value); // 具体
return this;
}
轮到具体子类 实现的方法 UnpooledHeapByteBuf
的 _setByte(int index, int value)
:
@Override
protected void _setByte(int index, int value) {
array[index] = (byte) value;
}
所以实际是对array的字节数组进行操作。
那 tmpNioBuf
变量什么时候用呢?
由于netty是对java nio的封装,所以最终底层通信仍然是使用ByteBuffer
进行,所以就是将array数组转化为 ByteBuffer
,从 internalNioBuffer
可见一斑。
private ByteBuffer internalNioBuffer() {
ByteBuffer tmpNioBuf = this.tmpNioBuf;
if (tmpNioBuf == null) {
this.tmpNioBuf = tmpNioBuf = ByteBuffer.wrap(array);
}
return tmpNioBuf;
}
还有一个内存分配器 ByteBufAllocator
。
每个ByteBuf
中都需要带有 ByteBufAllocator
说明他的内存分配类型,主要用于直接内存泄漏检测。
以 UnpooledByteBufAllocator
为例。
UnpooledByteBufAllocator
对于 堆内存,则是直接使用new方式获取一个 UnpooledByteBufAllocator:
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
return new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
而直接内存,则会通过包装一层 内存泄漏检测,后面文章分析。
@Override
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
ByteBuf buf;
if (PlatformDependent.hasUnsafe()) {
buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
} else {
buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
}
return toLeakAwareBuffer(buf);
}
UnpooledDirectByteBuf
UnpooledDirectByteBuf
主要成员变量为:
public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf {
private final ByteBufAllocator alloc;
private ByteBuffer buffer; // 操作的buffer
private ByteBuffer tmpNioBuf; // 用于转化为一个临时bytebuffer
private int capacity; // 当前容量
private boolean doNotFree; //
}
构造方法中,netty直接将java nio的 DirectByteBuffer
UnpooledDirectByteBuf
的 构造方法:
protected UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(maxCapacity);
...
setByteBuffer(ByteBuffer.allocateDirect(initialCapacity));
}
ByteBuffer
的 allocateDirect
方法:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
再看看 writeByte(int value)
方法:
前面和 UnpooledHeapByteBuf
基本一致,仍然看 _setByte(int index, int value)
方法:
@Override
protected void _setByte(int index, int value) {
buffer.put(index, (byte) value);
}
再UnpooledDirectByteBuf
的扩容过程:
AbstractByteBuf
的 ensureWritable
方法:
@Override
public ByteBuf ensureWritable(int minWritableBytes) {
...
// 计算大小,保证是2的倍数
int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes);
// 由子类实现具体大小.
capacity(newCapacity);
return this;
}
UnpooledDirectByteBuf
的 capacity
方法:
@Override
public ByteBuf capacity(int newCapacity) {
ensureAccessible();
if (newCapacity < 0 || newCapacity > maxCapacity()) {
throw new IllegalArgumentException("newCapacity: " + newCapacity);
}
int readerIndex = readerIndex();
int writerIndex = writerIndex();
int oldCapacity = capacity;
if (newCapacity > oldCapacity) {
ByteBuffer oldBuffer = buffer;
ByteBuffer newBuffer = allocateDirect(newCapacity);
oldBuffer.position(0).limit(oldBuffer.capacity());
newBuffer.position(0).limit(oldBuffer.capacity());
newBuffer.put(oldBuffer);
newBuffer.clear();
setByteBuffer(newBuffer);
} else if (newCapacity < oldCapacity) {
ByteBuffer oldBuffer = buffer;
ByteBuffer newBuffer = allocateDirect(newCapacity);
if (readerIndex < newCapacity) {
if (writerIndex > newCapacity) {
writerIndex(writerIndex = newCapacity);
}
oldBuffer.position(readerIndex).limit(writerIndex);
newBuffer.position(readerIndex).limit(writerIndex);
newBuffer.put(oldBuffer);
newBuffer.clear();
} else {
setIndex(newCapacity, newCapacity);
}
setByteBuffer(newBuffer);
}
return this;
}
总体思路即申请一个新本地内存,然后使用ByteBuffer
的 put
方法将旧内存放到新申请的 直接内存的 ByteBuffer
中。
关注博主公众号: 六点A君。
哈哈哈,一起研究Netty: