Netty核心组件之ByteBuf

309 阅读5分钟

概述

之前已经了解了NIO的三大组件 ByteBUffer Channel Selector 后,可以开始学习 Netty的相关知识。 Netty就是对NIO的组件进行了二次封装。这篇主要讨论 Netty 对 NIO 封装 image.png 从头往下看可以看到实现类分成池化和非池化,这部分具体的思想在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 方法操作指针方式如下图 image.png

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;

    }
 }

实现类

image.png 从实现类名称可以知道,创建的时候分成了池化和非池化.每个下面还分成堆和非堆,最终实现类如图

image.png

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));
    }
}