概述
之前已经了解了NIO的三大组件 ByteBUffer Channel Selector 后,可以开始学习 Netty的相关知识。 Netty就是对NIO的组件进行了二次封装。这篇主要讨论 Netty 对 NIO 封装
从头往下看可以看到实现类分成
池化和非池化,这部分具体的思想在Netty内存管理里面详解了,这里涉及到就会一笔带过。
ReferenceCounted
我们看到 ByteBuf实现了 ReferenceCounted接口
从名字就可以看得出来是引用计数器相关, Netty通过该接口管理 ByteBuf来实现 脱离于JVM的内存管理
public interface ReferenceCounted {
# 引用计数器 +1
ReferenceCounted retain();
# 引用计数器 -1
boolean release();
}
ByteBuf
public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {
# 写一个字节到 ByteBuf里
public abstract ByteBuf writeByte(int value);
# 从ByteBuf读取一个字节
public abstract byte readByte();
# 返回 readerIndex的指针下标
public abstract int readerIndex();
# 返回 writerIndex 的下标
public abstract int writerIndex();
# 把读过的数据废弃掉(但是不是清空)
public abstract ByteBuf discardReadBytes();
# 清空整个数组
public abstract ByteBuf clear();
# 返回创建了这个ByteBuffer的 allocator
public abstract ByteBufAllocator alloc();
}
ByteBuf 可以抽象的理解为一个数组,下标从0开始,有两个指针分别表示读取的指针和写入的指针。
discardReadBytes 和 clear 方法操作指针方式如下图
AbstractByteBuf
public abstract class AbstractByteBuf extends ByteBuf {
# 这个很重要之后会研究,好像是针对内存泄漏搞事情的
static final ResourceLeakDetector<ByteBuf> leakDetector =
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);
# 根据图上的抽象定义了几个指针
int readerIndex;
int writerIndex;
private int markedReaderIndex;
private int markedWriterIndex;
private int maxCapacity;
# 接下来的方法就是对这些指针的判断,然后实现了ByteBuf的一堆方法,然后自己又定义了一堆方法让子类实现,没什么好看的
}
AbstractReferenceCountedByteBuf
这个类看名字就知道肯定是实现了引用计数器接口相关的方法
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
# 这个是保证线程安全的类
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
# 引用计数器
private volatile int refCnt;
@Override
public ByteBuf retain() {
return retain0(1);
}
# 其实没啥东西,就是对这个计数器增加数字
private ByteBuf retain0(final int increment) {
int oldRef = refCntUpdater.getAndAdd(this, increment);
if (oldRef <= 0 || oldRef + increment < oldRef) {
// Ensure we don't resurrect (which means the refCnt was 0) and also that we encountered an overflow.
refCntUpdater.getAndAdd(this, -increment);
throw new IllegalReferenceCountException(oldRef, increment);
}
return this;
}
@Override
public boolean release(int decrement) {
return release0(checkPositive(decrement, "decrement"));
}
# 数量减到某一个值然后就要调用释放的方法,释放的方式由子类实现
private boolean release0(int decrement) {
int oldRef = refCntUpdater.getAndAdd(this, -decrement);
if (oldRef == decrement) {
deallocate();
return true;
} else if (oldRef < decrement || oldRef - decrement > oldRef) {
// Ensure we don't over-release, and avoid underflow.
refCntUpdater.getAndAdd(this, decrement);
throw new IllegalReferenceCountException(oldRef, decrement);
}
return false;
}
# 子类实现释放的方法
protected abstract void deallocate();
}
没有什么特别的东西,就是对一个数字加加减减,然后到了某一个值之后,让子类去释放数据,不同的类有不同的释放逻辑。
PooledByteBuf
池化的Bytebuf在内存分析的时候已经研究过,就是根据不同
abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {
# 也是一个回收管家等等研究
private final Recycler.Handle<PooledByteBuf<T>> recyclerHandle;
# 就是二叉树,逻辑管理内存地址
protected PoolChunk<T> chunk;
# 一个逻辑下标,通过运算可以算出当前ByteBuf在内存中使用的offset
protected long handle;
# 使用的内存,因为有堆和非堆之分,所以用泛型
protected T memory;
# handler算出来的下标
protected int offset;
# 从offset到length是当前ByteBuf可以使用的范围
protected int length;
int maxLength;
# 一个缓存,等等研究
PoolThreadCache cache;
private ByteBuffer tmpNioBuf;
private ByteBufAllocator allocator;
# 还记得引用计数器接口的deallocate么,在这里实现了
@Override
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
tmpNioBuf = null;
chunk.arena.free(chunk, handle, maxLength, cache);
chunk = null;
recycle();
}
}
private void recycle() {
recyclerHandle.recycle(this);
}
}
这么一看其实 PooledByteBuf没做什么东西,就是整合了几个二叉树跟内存在那边,读写都是子类实现,自己就实现了一个内存释放的操作
PooledHeapByteBuf & PooledDirectByteBuf
class PooledHeapByteBuf extends PooledByteBuf<byte[]> {
# 直接把内存里面的数据读到 JVM数组中
@Override
public final ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
checkDstIndex(index, length, dstIndex, dst.length);
System.arraycopy(memory, idx(index), dst, dstIndex, length);
return this;
}
# 内存数据读到 ByteBuffer里面
@Override
public final ByteBuf getBytes(int index, ByteBuffer dst) {
checkIndex(index, dst.remaining());
dst.put(memory, idx(index), dst.remaining());
return this;
}
# 直接从channel写到内存里面, 在Netty中常用
@Override
public final int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
return getBytes(index, out, length, false);
}
private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
checkIndex(index, length);
index = idx(index);
ByteBuffer tmpBuf;
if (internal) {
tmpBuf = internalNioBuffer();
} else {
tmpBuf = ByteBuffer.wrap(memory);
}
return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length));
}
}
heap和direct的基本上差不多,就一个JVM一个堆外的,没啥特别的。
UnpooledDirectByteBuf & UnpooledHeapByteBuf
Unpooled在ByteBuf层面api其实已经没差了,就是释放的时候跟池化的不一样,这里直接释放。没有放回池子的动作
@Override
protected void deallocate() {
ByteBuffer buffer = this.buffer;
if (buffer == null) {
return;
}
this.buffer = null;
if (!doNotFree) {
freeDirect(buffer);
}
}
其他相关类
大体的逻辑已经梳理完了,其中有些ByteBuf用到了其他的类,现在来看看到底是干嘛的
ResourceLeakDetector
一个监控所有ByteBuf的类,为了防止用户在使用ByteBuf结束后忘记调用release来释放ByteBuf,用这个类可以监控内存泄漏的情况。原理就是利用了WeakReference,然后被销毁的时候会通知队列,然后就判断泄漏情况.
至于如何告诉开发者,其实就是用日志的方式跟开发者说哪里泄漏了。
Recycler
等另外开一个专门的来讲
PoolThreadCache
这个会在Netty内存管理二里面讲到
ByteBufAllocator
在了解了 ByteBuf大概是一个什么东西之后,我们来看Netty是如何来创建 ByteBuf的.
public interface ByteBufAllocator {
ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
ByteBuf buffer();
ByteBuf buffer(int initialCapacity);
ByteBuf ioBuffer();
ByteBuf ioBuffer(int initialCapacity);
ByteBuf heapBuffer();
ByteBuf heapBuffer(int initialCapacity);
ByteBuf directBuffer();
ByteBuf directBuffer(int initialCapacity);
}
ByteBufAllocator 从名字就可以看出来是用来创建ByteBuf的工具。 分成 directBuffer,heapBuffer,ioBuffer。还有一个默认实现好的对象。从代码可以看到在PC端默认的实现对象是 PooledByteBufAllocator
public final class ByteBufUtil {
static final ByteBufAllocator DEFAULT_ALLOCATOR;
static {
# 判断是不是安卓应用,如果是的话走unpooled,因为安卓的内存空间不大,不能额外搞一个池子出来
String allocType = SystemPropertyUtil.get(
"io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
allocType = allocType.toLowerCase(Locale.US).trim();
ByteBufAllocator alloc;
if ("unpooled".equals(allocType)) {
alloc = UnpooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: {}", allocType);
} else if ("pooled".equals(allocType)) {
alloc = PooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: {}", allocType);
} else {
alloc = PooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
}
DEFAULT_ALLOCATOR = alloc;
}
}
实现类
从实现类名称可以知道,创建的时候分成了
池化和非池化.每个下面还分成堆和非堆,最终实现类如图
UnpooledByteBufAllocator
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
private final ByteBufAllocator alloc;
byte[] array;
private ByteBuffer tmpNioBuf;
public UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(maxCapacity);
checkNotNull(alloc, "alloc");
if (initialCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
}
this.alloc = alloc;
# 重点是这里,非池化的堆内存的ByteBuf 就是创建了一个堆的byte数组
setArray(new byte[initialCapacity]);
setIndex(0, 0);
}
}
来看看非池化的非堆 Bytebuf
public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf {
private ByteBuffer buffer;
public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(maxCapacity);
this.alloc = alloc;
# 重点就是这里,非池化的非堆ByteBuf底层就是一个非堆的ByteBuffer
setByteBuffer(ByteBuffer.allocateDirect(initialCapacity));
}
}