Netty---ByteBuf实现

一,获取

1.ByteBuf实现分类

 按底层实现分类

    1.HeadByteBuf: 底层实现为java堆内的字节数组,byte[],可以有GC回收。特点是内涵的分配和回收速度块,缺点是如果进行socket的io读写,需要额外做一次内存复制,将堆内存的内容复制到内核内存中,性能会有一定程度的下降。

    2.DirectByteBuf:直接内存字节缓冲区,非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收都相对较慢,但是读写操作,少了一次内存复制,速度比堆内存块。内核空间的字节数组,native堆中,由操作系统申请和管理,所以分配和释放速度较慢,因为调用方法要走更多的流程。

    3.CompositeByteBuf:上面两种方式的组合,也是一种零拷贝技术。内部实现组合两个缓冲区。

按内存回收角度分类:

    1.UnpooledByteBuf,没有使用对象池的普通bytebuf,

    2.PooledByteBuf:使用了对象池的bytebuf,避免了bytebuf的重复分配,回收,减少了GC的负担,但是内存池的管理和维护更加复杂,netty4.1默认使用对象池缓冲区。4.0默认使用非对象池缓冲区。

2.创建ByteBuf

ByteBuf的Allocator分配器:创建缓冲区和分配内存空间,提供了两种实现

        1.PoolByteBufAllocator:将ByteBuf实例放入池对象中,提高性能,将内存碎片减少到最小。

        2.UnpooledByteBufAllocator是普通未池化ByteBuf分配器,不会把ByteBuf放入池中,每次调用返回一个新的ByteBuf实例。

说明:我们先从 非池化的开始介绍,再介绍池化的,从简单到难

2.1 UnpooledHeadByteBuf

它分配的内存就在jvm管理的堆中,可以说是最简单的一个ByteBuf,它的内部就是一个byte[]组。

字段:

private final ByteBufAllocator alloc; //非池化 分配器
byte[] array;
private ByteBuffer tmpNioBuf;

我们先看它是如何通过xxxAllocator分配出来的

1.UnpooledByteBufAllocator.newHeapBuffer()方法

protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
    return PlatformDependent.hasUnsafe() ?
            new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
            new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}

 这里我们走第一个new对象,它是xxAllocator中的内部类,也就是本类,它的特点就是继承了我们需要实例化的对象,

private static final class InstrumentedUnpooledUnsafeHeapByteBuf extends UnpooledUnsafeHeapByteBuf {
    InstrumentedUnpooledUnsafeHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
        super(alloc, initialCapacity, maxCapacity);
    }

    它的构造方法,就是直接调用父类的构造方法,所以,我们就拿到了UnpooledHeapByteBuf的子类了。

2.UnpooledHeapByteBuf 构造方法

    它有两个构造方法,一个是根据大小自己初始化一个Byte[]数组,另外一个是直接赋值Byte[]数组,不用自己再去初始化。

public UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
    super(maxCapacity);
    if (initialCapacity > maxCapacity) { //抛出异常
       ....
    }
    this.alloc = checkNotNull(alloc, "alloc"); //检测是否为空
    setArray(allocateArray(initialCapacity));  //申请 创建一个初始的数组
    setIndex(0, 0);
}

重点方法就在这个SetArray上面,它是嵌套了,里面有一个分配的方法

直接,简单

protected byte[] allocateArray(int initialCapacity) {
    return new byte[initialCapacity];
}
private void setArray(byte[] initialArray) {
    array = initialArray;
    tmpNioBuf = null;
}

ByteBuf初始化之后,我们再来看它的读取

3.get/set方法

随机读取getInt()

public int getInt(int index) {
    ensureAccessible();
    return _getInt(index);
}
protected int _getInt(int index) {
    return HeapByteBufUtil.getInt(array, index); //使用工具类
}

工具类HeapByteBufUtil的具体实现,一个Int占4个byte。

static int getInt(byte[] memory, int index) {
    return  (memory[index]     & 0xff) << 24 |
            (memory[index + 1] & 0xff) << 16 |
            (memory[index + 2] & 0xff) <<  8 |
            memory[index + 3] & 0xff;
}

setInt的具体实现

static void setInt(byte[] memory, int index, int value) {
    memory[index]     = (byte) (value >>> 24);
    memory[index + 1] = (byte) (value >>> 16);
    memory[index + 2] = (byte) (value >>> 8);
    memory[index + 3] = (byte) value;
}

4.ByteBuf转ByteBuffer

public ByteBuffer nioBuffer(int index, int length) {
    ensureAccessible();
    return ByteBuffer.wrap(array, index, length).slice();
}

跳转到ByteBuffer中的wrap打包方法

public static ByteBuffer wrap(byte[] array,int offset, int length){
    try {
        return new HeapByteBuffer(array, offset, length);  //直接new一个出来
...
}

直接就根据内容,new了一个Nio ByteBuffer出来,转换逻辑简单。

2.2 UnpooledDirectByteBuf

它使用直接内存,交给操作系统分配,所以相比于Heap来说,复杂一些

字段:

private final ByteBufAllocator alloc;
ByteBuffer buffer; // accessed by UnpooledUnsafeNoCleanerDirectByteBuf.reallocateDirect()
private ByteBuffer tmpNioBuf;
private int capacity;
private boolean doNotFree; //是否释放

它与HeapByteBuf的直接不同点就是,它使用的是nio ByteBuffer来存在了,而HeapByteBuf使用的是数组。

1.newDirectBuffer()

位于UnpooledByteBufAllocator,非池化的ByteBuf的创建,都在这个分配器里面

protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    final ByteBuf buf;
    if (PlatformDependent.hasUnsafe()) {
        buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) :
                new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
    } else {
        buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
    }
    return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
}

还是一样的套路,内部类,我们看unsafeNoClearDirectByteBuf这个,调用父类构造方法,直接super(),来到我们的目标类中,先检测字段是否合法,最后直接allocateDirect()方法构建

public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
    super(maxCapacity);
    ObjectUtil.checkNotNull(alloc, "alloc");
    checkPositiveOrZero(initialCapacity, "initialCapacity");
    checkPositiveOrZero(maxCapacity, "maxCapacity");
    if (initialCapacity > maxCapacity) {
        throw new IllegalArgumentException(String.format(
                "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
    }

    this.alloc = alloc;
    setByteBuffer(allocateDirect(initialCapacity), false);
}

它调用的是ByteBuffer的allocateDirect()方法

protected ByteBuffer allocateDirect(int initialCapacity) {
    return ByteBuffer.allocateDirect(initialCapacity);
}

这个方法,直接返回一个nio的 DirectByteBuffer,所以在我们的目标类中,字段是nio的ByteBuffer就是这个原因,我们保存的是nio的DirectByteBuffer类

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

它的继承关系

来看DirectByteBuffer类的构造方法,传入的是初始大小

DirectByteBuffer(int cap) {                   // package-private
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();  //是否使用 页对齐
    int ps = Bits.pageSize();                //页大小,默认是4086b,4kb
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));  //如果使用页对齐,那么在基本的大小cap上,还要+页大小
    Bits.reserveMemory(size, cap);  //尝试申请内存  ★

    long base = 0;
    try {
        base = unsafe.allocateMemory(size);     //分配内存,返回基地址
    } catch (OutOfMemoryError x) { 
        Bits.unreserveMemory(size, cap);//没内存可分配,回滚修改的内存数据 throw x; 
    }
    unsafe.setMemory(base, size, (byte) 0);  //设置内存里的初始值为0
    if (pa && (base % ps != 0)) {        //地址对齐,且基地址不是页大小的整数倍
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));  //将地址改为页大小的整数倍
    } else {
        address = base;   //地址就是基地址
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));  //创建内存回收器,传的是基地址
    att = null;  //没附件
}

    页对齐的,是使用空间换时间,前面一个页没有使用完毕,那么我们新加入的数据,可能会一部分放在前面一个页,一部分放在新页上面,我们增加一个页空间,不管什么情况,我们都可以把自己全部内容换到一个新的页,占用该页的开头位置,(多余的空间就用来填补前一个页余下的空间,这里就是浪费空间的意思),而之前如果一个数据放在两个页上面就会加载两次,现在我们只需要加载一次,就达到了空间换时间的目的。

    具体的分配:Bits.reserveMemory()

static void reserveMemory(long size, int cap) {
    if (!memoryLimitSet && VM.isBooted()) {
        maxMemory = VM.maxDirectMemory();   //获取分配的上限
        memoryLimitSet = true;
    }
    // optimist!  看是否还能申请内存,成功就返回
    if (tryReserveMemory(size, cap)) {
        return;
    }
    final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();

    // retry while helping enqueue pending Reference objects
    // which includes executing pending Cleaner(s) which includes
    // Cleaner(s) that free direct buffer memory
    while (jlra.tryHandlePendingReference()) {
        if (tryReserveMemory(size, cap)) {  //再次尝试申请
            return;
        }
    }
    System.gc();  //如果没有成功,就启动gc

    // a retry loop with exponential back-off delays
    // (this gives VM some time to do it's job)
    boolean interrupted = false;
    try {
        long sleepTime = 1;
        int sleeps = 0;
        while (true) {
            if (tryReserveMemory(size, cap)) {
                return;
            }
            if (sleeps >= MAX_SLEEPS) {  //尝试等待9次睡眠,大概0.5秒,如果还没内存就退出循环
                break;
            }
            if (!jlra.tryHandlePendingReference()) {//如果没有释放内存就sleep
                try {
                    Thread.sleep(sleepTime);
                    sleepTime <<= 1;  //睡眠时间x2
                    sleeps++;
                } catch (InterruptedException e) {
                    interrupted = true;
                }
            }
        }

        // no luck  没有内存可以申请,抛出异常
        throw new OutOfMemoryError("Direct buffer memory");

    } finally {
        if (interrupted) {
            // don't swallow interrupts
            Thread.currentThread().interrupt();
        }
    }
}

这里就要涉及底层的东西了,我们先到这里,详细内容,可以看下方的参考链接,超级详细。

 

2.3 池化ByteBuf

这个内容,就更加复杂了,也可以直接参考下方的链接,我就不当搬运工了!

二,释放

    在流水线的末尾添加TailHandler处理器,进行自动释放。

    但是如果流水线没有走完,而是走到了一半就截断了,这个时候要嘛就是手动释放,

    或者SimpleChannelInbounHandler进行释放。

一共上述三种情况。

(自动释放)https://blog.csdn.net/wangwei19871103/article/details/104679073

参考链接:https://blog.csdn.net/wangwei19871103/article/details/104235590

                https://blog.csdn.net/wangwei19871103/article/details/104264130

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值