Netty学习之旅------第4篇---ByteBuf的扩容、缩容和类继承关系

本文深入探讨Netty中的ByteBuf机制,包括其扩容、缩容策略及类继承关系,详细分析堆内与堆外内存的管理方式,以及Netty内存分配体系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

温馨提示:内容局限于本人的理解,如果有错误,请指正,谢谢!
学习目标:
(1)了解ByteBuf的扩容机制
(2)了解ByteBuf 的基本类继承关系
(3)了解ByteBufAllocator(内存分配)的类继承关系

1. ByteBuf 的扩容和缩容

上一篇说到,ByteBuf基于ByteBuffer的痛点做了优化,比如扩容就是很重要的一项优化,下面以writeBytes(byte[] src)为例来看下它的扩容机制。首先,在ByteBuf中只是定义了抽象方法,扩容的代码在AbstractByteBuf中

1.1 容量计算

@Override
    public ByteBuf writeBytes(byte[] src) {
        writeBytes(src, 0, src.length);//@1
        return this;
    }
@Override
    public ByteBuf writeBytes(ByteBuf src, int length) {
        if (length > src.readableBytes()) {
            throw new IndexOutOfBoundsException(String.format(
                    "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src));
        }
        writeBytes(src, src.readerIndex(), length);//@1
        src.readerIndex(src.readerIndex() + length);//@2
        return this;
    }

@1 扩容逻辑
@2 更新readerIndex
继续进入@1

	@Override
    public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
        ensureAccessible();//@1
        ensureWritable(length);//@2
        setBytes(writerIndex, src, srcIndex, length);
        writerIndex += length;
        return this;
    }
	@Override
    public ByteBuf ensureWritable(int minWritableBytes) {
        if (minWritableBytes < 0) {
            throw new IllegalArgumentException(String.format(
                    "minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }
        ensureWritable0(minWritableBytes);
        return this;
    }
	private void ensureWritable0(int minWritableBytes) {
        if (minWritableBytes <= writableBytes()) {//@3
            return;
        }

        if (minWritableBytes > maxCapacity - writerIndex) {//@4
            throw new IndexOutOfBoundsException(String.format(
                    "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
                    writerIndex, minWritableBytes, maxCapacity, this));
        }

        // Normalize the current capacity to the power of 2.
        int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);@5

        // Adjust to the new capacity.
        capacity(newCapacity);
    }

@1 检查缓冲区是否可以访问,检查refCnt==0会被回收
@2 传入待写字节的长度,进行扩容,确保可以写入。
@3 判断写入的长度,如果当前缓冲区可以写入,直接结束,无需扩容。
@4 写入的内容长度大于可写的最大容量,则抛出IndexOutOfBoundsException异常
@5 就是扩容了,扩容有2中方式,一种是申请堆内内存,一种申请堆外内存,下面来看下具体的实现
calculateNewCapacity就是计算新的ByteBuf的大小

	@Override
    public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
        if (minNewCapacity < 0) {
            throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expected: 0+)");
        }
        if (minNewCapacity > maxCapacity) {
            throw new IllegalArgumentException(String.format(
                    "minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
                    minNewCapacity, maxCapacity));
        }
        final int threshold = CALCULATE_THRESHOLD; // 4 MiB page  //@6

        if (minNewCapacity == threshold) {
            return threshold;
        }

        // If over threshold, do not double but just increase by threshold.
        if (minNewCapacity > threshold) {@7
            int newCapacity = minNewCapacity / threshold * threshold;
            if (newCapacity > maxCapacity - threshold) {
                newCapacity = maxCapacity;
            } else {
                newCapacity += threshold;
            }
            return newCapacity;
        }

        // Not over threshold. Double up to 4 MiB, starting from 64.
        int newCapacity = 64;
        while (newCapacity < minNewCapacity) {@8
            newCapacity <<= 1;
        }

        return Math.min(newCapacity, maxCapacity);
    }

@6 定义了一个常量4MB每页
@7 如果申请最小的容量刚好等于4MB,则直接返回4MB.如果申请的大小超过了4MB,则需要重新计算新容量大小= minNewCapacity / threshold * threshold;也就是不会翻倍的扩容,只会以4MB为基数,在申请的容量上,进行扩容,比如:
threshold =4
maxCapacity =20

写入大小minNewCapacitynewCapacity
第一组6(6/4*4)+4= 8
第二组8(8/4*4)+4= 12
第三组19(19/4*4)+4= 20
第三组20(20/4*4)>20-4,直接返回20

@8 以64位基础,每次翻倍扩容,64–>128–>256–>512–>1024…
原理就是满足你写入的大小,在缓冲区还不大的情况下,直接以64MB为基础,翻倍扩容,当缓冲区达到一定的大小,就采用以4MB为基数来扩容,这样的设计的好处是,往往缓冲区较小,需要写入的数据比较大,为了避免频繁的扩容,影响效率,才一次性扩容的粒度较大,如果数据持续增加,再以更小的粒度进行扩容,扩容完成后,调用setBytes子类的方法,进行写入。
经过上面的步骤,已经计算出了缓冲区所需的大小,接下来就是申请缓冲区的空间了。因为缓冲区分为堆内内存和堆外内存2中,所以有不同的实现,堆内比较简单,先来看下具体实现。

1.2 堆内内存的扩容和缩容

UnpooledHeapByteBuf#capacity

    @Override
    public ByteBuf capacity(int newCapacity) {
        checkNewCapacity(newCapacity);

        int oldCapacity = array.length;
        byte[] oldArray = array;
        if (newCapacity > oldCapacity) {@1
            byte[] newArray = allocateArray(newCapacity);
            System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            setArray(newArray);
            freeArray(oldArray);
        } else if (newCapacity < oldCapacity) {@2
            byte[] newArray = allocateArray(newCapacity);
            int readerIndex = readerIndex();
            if (readerIndex < newCapacity) {@3
                int writerIndex = writerIndex();
                if (writerIndex > newCapacity) {
                    writerIndex(writerIndex = newCapacity);
                }
                System.arraycopy(oldArray, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
            } else {
                setIndex(newCapacity, newCapacity);@4
            }
            setArray(newArray);@5
            freeArray(oldArray);@6
        }
        return this;@7
    }

@1 如果当前所需的大小大于旧缓冲区的最大容量,进行扩容,则通过System.arraycopy拷贝到新的数组中,然后释放旧的数组(因为堆内存储是存储在数组中)。
@2 如果当前所需大小小于旧缓冲区的最大容量,进行缩容,也通过allocateArray新建一个新的byte数组newArray
@3 如果有未读的数据,关系如:readerIndex< newCapacity < writerIndex,则更新writeIndex的值为新缩容后的大小。并通过System.arraycopy方法,将旧ByteBuf中未读数据拷贝到新的ByteBuf中
@4 如果 readerIndex < writerIndex < newCapacity 关系成立,直接更新readerIndex=writerIndex=newCapacity
@5 将新的数组赋值到当前缓冲区的array
@6 释放旧的数组
@7 无需扩容和缩容,直接返回当前的缓冲区。

1.3 堆外内存的扩容和缩容

UnpooledDirectByteBuf#capacity

    @Override
    public ByteBuf capacity(int newCapacity) {
        checkNewCapacity(newCapacity);

        int readerIndex = readerIndex();
        int writerIndex = writerIndex();

        int oldCapacity = capacity;
        if (newCapacity > oldCapacity) {@1
            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) {@2
            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;
    }

@1 需要扩容,申请一块大小为newCapacity的新ByteBuffer newBuffer,将oldBuffer、newBuffer的position=0,limit为oldBuffer.capacity,等于将2个缓冲区对齐。将oldBuffer put到新的newBuffer里面,设置newBuffer的position = 0、 limit = capacity、mark = -1。将newBuffer 赋值到当前的缓冲区,并更新当前缓冲区的capacity=newBuffer的limit-position,在读的模式下,那capacity就是当前有内容的长度,在写的模式下,那就是可写长度的。
@2 需要缩容,满足readerIndex < newCapacity < writerIndex 关系,更新writerIndex=newCapacity,设置oldBuffer、newBuffer缩容后的大小,将2缓冲区对齐,将oldBuf put newBuffer, 到如果满足readerIndex < writerIndex < newCapacity 关系,说明缩容后,没有影响到之前的读写区域。 直接更新readerIndex=writerIndex=newCapacity
@3 无需扩容和缩容,直接返回当前的缓冲区。

缩容和扩容也涉及池化的机制,即在池化的缓冲区中如何缩容和扩容,这些在后面的博文里面再介绍。

2. Netty ByteBuf 主要功能继承图

在这里插入图片描述

类名描述
ByteBuf缓冲区的顶层抽象类,子类主要分2大类,一类是池化(对象池)类,另外一类是非池化类。例如:UnpooledUnsafeDirectByteBuf、UnpooledDirectByteBuf、UnpooledHeapByteBuf都是非池化的子类,因为不管池化和非池化,都有堆内存、堆外内存。
AbstractByteBuf继承自ByteBuf,提供了公共方法的实现,差异化的功能由子类实现。
AbstractReferenceCountedByteBuf主要管理缓冲区的引用次数,每调用一次retain方法,refCnt会加1,每调用一次release方法,refCnt减1, 当refCnt=0,该缓冲区是会被回收掉的。
UnpooledDirectByteBuf非池化+使用堆外内存的Buf
UnpooledHeapByteBuf非池化+堆内存的Buf
PooledDirectByteBuf池化+堆外内存的Buf
PooledHeapByteBuf池化+堆内存的Buf

3. Netty内存分配继承图

在这里插入图片描述

类名描述
ByteBufAllocator顶层内存分配接口,定义了公共的分配方法
AbstractByteBufAllocator实现自ByteBufAllocator接口
UnpooledByteBufAllocator继承自AbstractByteBufAllocator,分配非池化的ByteBuf
PooledByteBufAllocator继承自AbstractByteBufAllocator,分配池化的ByteBuf

4. 总结

从内存分配的角度,主要分为堆内内存和堆外内存的分配。从内存回收的角度,可以分为池化和非池化,池化就是内部维护了一个对象池,可以重用ByteBuf对象。在高负载的情况下,池化的表现更加平稳,可以降低高负载带来的频繁GC,单池化的内部实现要复杂,使用的时候也要更加谨慎。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值