详解Netty ByteBuffer的实现机制和原理

本文详细介绍了Netty中ByteBuffer的实现,包括内存池机制提升性能,以及读写操作的双指针管理。Netty通过PooledByteBufAllocator实现内存池,减少了GC开销,并通过抽象的引用计数算法实现内存的高效管理。此外,还强调了在使用过程中应注意的线程安全和释放问题。
摘要由CSDN通过智能技术生成

应用在进行数据传输时,往往需要用到缓冲区,常见的缓冲区是JDK NIO类库提供的Buffer,由于JDK提供的Buffer存在一些问题,比如要求定长,读写的时候需要切换等问题,所以Netty提供了自己ByteBuffer的实现,功能更丰富,使用起来也更加方便。

一、创建方式

Netty做为高性能的NIO通信框架,在4.x版本引入了内存池机制,默认采用内存池的方式创建ByteBuf对象,性能有了很大的提升,有效降低了GC的开销,性能上会有很大的提高。

// 传统模式
ByteBuf buf = Unpooled.buffer(10 * 1024);

// 使用内存池
PooledByteBufAllocator allocator = new PooledByteBufAllocator(false);
ByteBuf buf = allocator.heapBuffer(10 * 1024);

内存池就跟线程池的思想类似,只是维护的不是线程,而是一块块的内存区域,Netty的内存池分为PooledArea、PoolChunk和PoolSubpage。PoolArea代表内存中一片连续的区域,由多个PoolChunk组成,而PoolChunk是来组织和管理多个Page,默认16M,每个PoolChunk包括2048个Page,每个Page是8k。这里截取了一张关于PoolChunk内存分配的示例图:

在这里插入图片描述

PoolSubpage则是对于小于一个Page的内存处理的,如果存储的数据小于8k,那么Page会被切分成大小相等的多个存储块,具体存储快的大小由第一次申请的内存块大小决定。比如申请的内存块为2k,那么该Page就只能继续分配2k的数据,如果由一个申请了4k,那么就需要在新的Page分配。

二、分配机制

ByteBuffer本质上也是一个Byte数组的缓冲区,可以提供以下几类功能:

1:支持Java基础类型、byte数组、ByteBuffer等的读写
2:缓冲区自身的copy和slice
3:操作位置指针,双指针读写互不干扰
4:容量自动扩容
5:能够灵活切换到其他数据结构,比如JDK的ByteBuffer

不同于JDK ByteBuffer,Netty的ByteBuffer通过两个指针来协助缓冲区的读写操作,读操作使用readerIndex,写操作使用writerIndex。readerIndex和writerIndex其实位置都是0,随着写入writeIndex会增加,读取数据readerIndex会增加,但是readerIndex始终是不会超过writerIndex,在0~readIndex则是可以被丢弃,readIndex和writeIndex之间的数据则是可以被读取,整体可以类比成一个环,双指针的操作,跟MySQL RedoLog的操作是类似的思想。

由于读写之间是采用不同的指针,因为读写操作不需要调整位置指针,这极大的简化了缓冲区读写操作,避免了对于JDK ByteBuffer的flip()操作导致功能异常。而0~readIndex这部分数据已经被读取,可以调用discardReadBytes()方法释放这部分空间,从而达到复用ByteBuffer,防止ByteBuffer不断扩展的目的。而调用clear()方法则直接重置了读写指针。

三、内存管理

AbstractReferenceCountedByteBuf通过引用计数算法实现对ByteBuffer的管理,跟踪对象的分配和使用,以实现内存的回收、销毁和重利用。

在这里插入图片描述

上面是它继承关系图,其中refCnt是引用计数器,勇于对ByeBuf的申请和释放做计数,它的更新通过源自类AtomicIntegerFieldUpdater处理。示例代码如下:

public ByteBuf retain() {
    return this.retain0(1);
}

private ByteBuf retain0(int increment) {
    int oldRef = refCntUpdater.getAndAdd(this, increment);
    if (oldRef > 0 && oldRef + increment >= oldRef) {
        return this;
    } else {
        refCntUpdater.getAndAdd(this, -increment);
        throw new IllegalReferenceCountException(oldRef, increment);
    }
}

public boolean release() {
    return this.release0(1);
}
    
private boolean release0(int decrement) {
    int oldRef = refCntUpdater.getAndAdd(this, -decrement);
    if (oldRef == decrement) {
        this.deallocate();
        return true;
    } else if (oldRef >= decrement && oldRef - decrement <= oldRef) {
        return false;
    } else {
        refCntUpdater.getAndAdd(this, decrement);
        throw new IllegalReferenceCountException(oldRef, -decrement);
    }
}

refCnt使用volatile解决多线程并发之间线程可见的问题,同时也避免了指令重排。所以每调用一次retain()方法,计数器就+1,如果累加后计数器小于0或者发生数组越界,那么则抛出IllegalReferenceCountException异常。调用release()方法释放引用,如果oldRef等于decrement,则说明对象申请和释放次数相同,refCnt为0,ByteBuf已经没有被引用了,那么就执行释放操作。

需要注意的是,不同的ByteBuf模式,deallocate释放的策略不同,如果是内存池模式,则需要将ByteBuf返回到内存池中以便复用。

protected final void deallocate() {
    if (this.handle >= 0L) {
        long handle = this.handle;
        this.handle = -1L;
        this.memory = null;
        this.tmpNioBuf = null;
        this.chunk.arena.free(this.chunk, handle, this.maxLength, this.cache);
        this.chunk = null;
        this.recycle();
    }
}

如果是内存模式,直接设置为空,等待GC回收。

protected void deallocate() {
    ByteBuffer buffer = this.buffer;
    if (buffer == null) {
        return;
    }
    this.buffer = null;
    if (!doNotFree) {
        freeDirect(buffer);
    }
}
四、需要注意的点

虽然Netty的ByteBuffer给我们提供了,但在使用还是要注意一些点,整理如下:
1:ByteBuffer是非线程安全的,所以申请和释放要防止跨NioEventLoop线程与应用线程并发操作;
2:ByteBuffer需要特别注意Bytebuffer被Netty隐式释放的问题,避免应用要用到的时候被Netty隐式释放掉了;
3:性能问题,通常的获取get()、set()方法并不会有性能问题,但要避免在其中做复杂的操作。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芋圆在睡觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值