netty directbytebuffer_java读源码之netty

netty directbytebuffer_java读源码之netty一、背景简介ByteBuf,顾名思义,就是字节缓冲区,是Netty中非常重要的一个组件

一、背景简介

ByteBuf,顾名思义,就是字节缓冲区,是Netty中非常重要的一个组件。熟悉jdk NIO的同学应该知道ByteBuffer,正是因为jdk原生ByteBuffer使用比较复杂,某些场景下性能不是太好,netty开发团队重新设计了ByteBuf用以替代原生ByteBuffer。
 

二、ByteBuf和ByteBuffer对比

下面用图示来展示ByteBuf和ByteBuffer工作原理:

①、ByteBuffer

netty directbytebuffer_java读源码之netty

 ByteBuffer依靠flip()来切换模式,在读模式下调用flip()切换为写模式,在写模式下limit和capacity相等,position标识当前写的位置。在写模式下调用flip()切换为读模式,在读模式下position回到起始位置开始读,limit回到position位置表示能读到多少数据,capacity不变表示缓存区容量大小。

capacity:在读/写模式下都是固定的,就是缓冲区容量大小。

position:读/写位置指针,表示当前读(写)到什么位置。

limit:在写模式下表示最多能写入多少数据,此时和capacity相同。在读模式下表示最多能读多少数据,此时它的值等于缓存区中实际数据量的大小。
 

②、ByteBuf

ByteBuf主要是通过readerIndex 和 writerIndex两个指针进行数据的读和写,整个ByteBuf被这两个指针最多分成三个部分,分别是可丢弃部分,可读部分和可写部分

刚初始化的时候,整个缓冲区还没有数据,读写指针都指向0,所有的内容都是可写部分,此时还没有可读部分和可丢弃部分,如下:

netty directbytebuffer_java读源码之netty

 当写完N个字节数据后,读指针仍然是0,因为还没有开始进行读事件,写指针向后移动了N个字节的位置,如下:

netty directbytebuffer_java读源码之netty

 当开始读数据并且读取M个字节数据之后(M<N)写指针位置不变,读指针后移动了M个字节的位置,如下:

netty directbytebuffer_java读源码之netty

 当可丢弃部分数据被清空之后,readerindex重新回到起始位置,writerindex的位置为writerindex的值减去之前的readerindex,也就是M,相关图示如下:

netty directbytebuffer_java读源码之netty

 调用clear之后,writerindex和readerinde全部复位为0。它不会清除缓冲区内容(例如,用填充0),而只是清除两个指针。更改的读写指针的值,每个位置上原本的字节内容并没有发生改变,只是变成了可写状态而已。另请注意,此操作的语义不同于Buffer.clear()。

netty directbytebuffer_java读源码之netty

三、源码

明白了ByteBuf工作原理之后,ByteBuf相关的api就很好理解了,在此附上netty官方api文档,以供参阅:

https://netty.io/4.1/api/overview-summary.html。

我们在这里看下netty扩容相关源码逻辑。

扩容肯定是在写入数据的时候会由相关逻辑判断,我们随便进入一个写入字节的api方法。
 

public abstract ByteBuf writeBytes(byte[] src);

进入到其抽象子类AbstractByteBuf中。

    @Override
    public ByteBuf writeBytes(byte[] src) {
        writeBytes(src, 0, src.length);
        return this;
    }
    @Override
    public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
        ensureAccessible();
        ensureWritable(length);
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }

首先ensureAccessible进行安全校验,每种尝试访问缓冲区内容的方法都应调用此方法,以检查缓冲区是否已释放。然后ensureWritable判断是否可写,扩容相关逻辑就在这里进行判断,如果缓冲区可写执行setBytes进行数据写入,然后writerindex向后移动length的位置,最后将ByteBuf对象进行返回。我们重点看ensureWritable。

    @Override
    public ByteBuf ensureWritable(int minWritableBytes) {
        if (minWritableBytes < 0) {
            throw new IllegalArgumentException(String.format(
                    "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }
        ensureWritable0(minWritableBytes);
        return this;
    }

直接进入ensureWritable0(minWritableBytes)方法中,此时minWritableBytes就是我们计划需要申请的内存大小空间。

        private void ensureWritable0(int minWritableBytes) {
         // 安全检查,保证写入之前是可访问的
         //ensureAccessible();
​
         // 可写,不必扩容
        if (minWritableBytes <= writableBytes()) {
            return;
        }
​
        //下标越界
        if (minWritableBytes > maxCapacity - writerIndex) {
            throw new IndexOutOfBoundsException(String.format(
                    "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                    writerIndex, minWritableBytes, maxCapacity, this));
        }
​
        //达到临界条件,开始执行扩容逻辑
        // 计算新的容量,实际上为当前容量扩容至2的幂次方大小(具体是多少需要进行后续判断和计算)
        int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
        // 扩容后的容量
        capacity(newCapacity);
    }

可以看到真正开辟内存空间新容量逻辑处理的是 alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity)执行的,进入到方法里面。

来到其实现类AbstractByteBufAllocator的calculateNewCapacity方法。

     @Override  
    public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
        if (minNewCapacity < 0) {
            throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expectd: 0+)");
        }
        if (minNewCapacity > maxCapacity) {
            throw new IllegalArgumentException(String.format(
                    "minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
                    minNewCapacity, maxCapacity));
        }
        // 扩容的阈值,4兆字节大小
        final int threshold = 1048576 * 4;
        if (minNewCapacity == threshold) {
            return threshold;
        }
       //如果计划一共需要的内存容量大小大于阈值,则需要和最大容量j进行比较
        if (minNewCapacity > threshold) {
            int newCapacity = minNewCapacity / threshold * threshold;
            if (newCapacity + threshold > maxCapacity) {
                newCapacity = maxCapacity;
            } else {
                newCapacity += threshold;
            }
            return newCapacity;
        }
​
        //如果计划一共需要的内存容量大小小于阈值,则以64为基数进行倍增
        int newCapacity = 64;
        while (newCapacity < minNewCapacity) {
            newCapacity <<= 1;
        }
        return Math.min(newCapacity, maxCapacity);
    }

minNewCapacity是我们计划一共需要的内存容量大小,maxCapacity是最大缓冲区容量大小。首先判断minNewCapacity 是否小于零或者minNewCapacity 是否大于maxCapacity,满足任一都抛出异常信息,然后判断我们计划一共需要的内存容量大小minNewCapacity 是否等于了阈值4M:

①、如果等于了阈值,新容量大小就是阈值4M。

②、如果计划一共需要的内存容量大小大于阈值,则maxCapacity和minNewCapacity 相对于阈值的整数倍再加上一个阈值进行大小判断,如果大于maxCapacity,则新容量最大就是maxCapacity,返回maxCapacity,如果小于maxCapacity,则相当于按照阈值的2倍进行扩容。

③、如果计划一共需要的内存容量大小小于阈值,则以64为基数只要小于我们计划需要的内存容量大小,就2倍扩容,最后选取循环后的扩容值和最大值两个值其中的较小者。

至此扩容就完成了,总结来说就是在扩容过程中有一个扩容需要容量的一个阈值4M,如果我们需要的内存空间等于这个阈值,那么扩容后的容量就是阈值大小,如果我们需要的内存容量大小大于阈值或者小于阈值,其扩容逻辑判断和扩容后返回的容量大小是不同的。但是最终扩容后的容量大小总是2的幂次方大小并且不会比maxCapacity大。
 

4、ByteBuf主要的继承关系

netty directbytebuffer_java读源码之netty

从内存分配的角度看,ByteBuf可以分为两类

(1)堆内存(HeapByteBuf)字节缓冲区:特点是内存的分配和回收速度快,可以被JVM自动收回;缺点就是如果进行Socket的I/O读写,需要额外做一次内存复制,将堆内存对应的缓冲区复制到内核Chanenel中,性能会有一定程度的下降。

(2)直接内存(DirectByteBuf) 字节缓冲区:非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收速度会慢一些,但是将它写入或者从Socket Channel中读取时,由于少了一次内存复制,速度比堆内存快。

正式因为各有利弊,所以Netty提供了多种ByteBuf供开发者使用,经验表明,ByteBuf的最佳实践是在I/O通信线程的读写缓冲区使用DirectByteBuf,后端业务消息的编解码模块使用HeapByteBuf,这样组合可以达到性能最优。

从内存回收角度看,ByteBuf也可以分为两类:基于对象池的ByteBuf和普通ByteBuf。两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护了一个内存池,可以循环利用创建的ByteBuf,提升内存的使用效率,降低由于高负载导致的频繁GC。测试表名使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。

今天的文章netty directbytebuffer_java读源码之netty分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/78405.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注