Netty ByteBuf和Nio ByteBuffer
参考https://blog.csdn.net/jeffleo/article/details/69230112
一、简介
Netty中引入了ByteBuf,它相对于ByteBuffer来说,带来了很多便捷性和创新的地方,使得程序员更简单得进行网络编程
二、ByteBuffer的缺点和ByteBuf的改进
下面我们从几个点来分别讲解:
1. 扩容
ByteBuffer:
ByteBuffer缓冲区的长度固定,分多了会浪费内存,分少了存放大的数据时会索引越界,所以使用ByteBuffer时,为了解决这个问题,我们一般每次put操作时,都会对可用空间进行校检,如果剩余空间不足,需要重新创建一个新的ByteBuffer,然后将旧的ByteBuffer复制到新的ByteBuffer中去。
ByteBuf:
而ByteBuf则对其进行了改进,它会自动扩展,具体的做法是,写入数据时,会调用ensureWritable方法,传入我们需要写的字节长度,判断是否需要扩容:
public ByteBuf ensureWritable(int minWritableBytes) {
if(minWritableBytes < 0) {
throw Exception
} else if(minWritableBytes <= this.writableBytes()) {
return this;
} else if(minWritableBytes > this.maxCapacity - this.writerIndex) {
throw Exception
} else {
//扩容
int newCapacity = this.calculateNewCapacity(this.writerIndex + minWritableBytes);
this.capacity(newCapacity);
return this;
}
}
private int calculateNewCapacity(int minNewCapacity) {
int maxCapacity = this.maxCapacity;
int threshold = 4194304; //4mb阀值
if(minNewCapacity == 4194304) {//如果新容量为阀值,直接返回
return 4194304;
} else {
int newCapacity;
if(minNewCapacity > 4194304) {//如果传入的新容量大于阀值,进行计算
newCapacity = minNewCapacity / 4194304 * 4194304;
if(newCapacity > maxCapacity - 4194304) {//如果大于最大容量,新容量为最大容量
newCapacity = maxCapacity;
} else {//否则新容量 + 阀值 4mb,按照阀值扩容
newCapacity += 4194304;
}
return newCapacity;
} else {//如果小于阀值,则以64为计数倍增,知道倍增的结果>=需要的容量值
for(newCapacity = 64; newCapacity < minNewCapacity; newCapacity <<= 1) {
;
}
return Math.min(newCapacity, maxCapacity);
}
}
}
这里要解释的地方:
1. 当申请的新空间大于阀值时,采用每次步进4MB的方式进行扩张内存,而不是倍增,因为这会造成内存膨胀和浪费
2. 而但申请的新空间小于阀值时,则以64为基数进行倍增而不是步进,因为当内存比较小的时候,倍增是可以接受的(64 -> 128 和 10Mb -> 20Mb相比)
2. 位置指针
ByteBuffer:
ByteBuffer中只有一个位置指针position(ByteBuf有两个),所以需要我们手动得调用flip等方法,例如:
ByteBuffer buffer = ByteBuffer.allocate(88);
String value = "我的博客";
buffer.put(value.getBytes());
buffer.flip();
byte[] array = new byte[buffer.remaining()];
buffer.get(array);
String decodeValue = new String(array);
ByteBuffer中会有三个下标,初始位置0,当前位置positon,limit位置,初始时,position为0,limit为Buffer数组末尾
调用buffer.put(value.getBytes())后:
不调用flip:
从缓冲区读取的是position — limit位置的数据,明显不是我们要的
调用flip:
会将limit设置为position,position设置为0,,此时读取的数据
ByteBuf:
ByteBuf中使用两个指针,readerIndex,writerIndex来指示位置,初始时readrIndex = writerIndex = 0,当写入数据后:
writerIndex — capacity:可写容量
readerIndex — writerIndex:可读部分
当读取了M个字节后:
调用discardReadBytes,会释放掉discardReadBytes的空间,并把readableBytes复制到从0开始的位置,因此这里会发生内存复制,频繁调用会影响性能
三、ByteBuf分析
ByteBuf最重要的两个基类,AbstractByteBuf提供了骨干实现,AbstractReferenceCountedByteBuf是AbstractByteBuf的子类,它的子类很常使用
1. 分类
1.1 从内存分配角度分类:
(1)堆内存字节缓冲区(HeapByteBuf):UnPoolHeapByteBuf、PooledHeapByteBuf
它的特点是内存的分配和回收都在堆,所以速度很快;缺点就是进行Socket的IO读写,需要把堆内存对应的缓冲区复制到内核Channel中,这内存复制会影响性能
(2)直接内存缓冲区(DirectByteBuf):UnPoolDirectByteBuf、UnPoolUnsafeDirectByteBuf、PoolDirectByteBuf、PoolUnsafeDirectByteBuf
它的特点是由于内存的分配在非堆(方法区),不需要内存复制,所以IO读取的速度较快,但是内存的分配较慢
总结:
根据两种内存的特点,我们可以知道,IO读写时最好使用DirectByteBuf,而在后端业务消息的解编码最好使用HeapByteBuf
1.2 从内存回收的角度分类
(1)基于对象池的ByteBuf(PoolByteBuf):PooledByteBuf和它的子类PoolDirectByteBuf、PoolUnsafeDirectByteBuf、PooledHeapByteBuf
它的特点是可以循环利用创建的ByteBuf,提高了内存的使用效率,PoolByteBuf的实现牵涉的数据结构很多,PoolByteBuf首先会申请一大块内存区域PoolArena,PoolArena由多个Chunk组成,而每个Chunk由一个或多个page组成
具体看《Netty权威指南》;
也可以看:
深入浅出Netty内存管理 PoolChunk
深入浅出Netty内存管理 PoolSubpage
深入浅出Netty内存管理 PoolChunkList
深入浅出Netty内存管理 PoolArena
(2)普通的ByteBuf(UnPoolByteBuf):UnPoolDirectByteBuf、UnPoolUnsafeDirectByteBuf、UnPoolHeapByteBuf
总结:
在高负载,大并发的情况下对象池的ByteBuf更好,而在一般情况下,可以使用UnPoolByteBuf
2. Netty的零拷贝
Netty的零拷贝主要体现在三个方面:
第一种实现:DirectByteBuf
就如上所说,ByteBuf可以分为HeapByteBuf和DirectByteBuf,当使用DirectByteBuf可以实现零拷贝
第二种实现:CompositeByteBuf
CompositeByteBuf将多个ByteBuf封装成一个ByteBuf,对外提供封装后的ByteBuf接口
第三种实现:DefaultFileRegion
DefaultFileRegion是Netty的文件传输类,它通过transferTo方法将文件直接发送到目标Channel,而不需要循环拷贝的方式,提升了传输性能
3. Netty的内存回收管理
Netty会通过 引用计数法 及时申请释放不再被引用的对象 ,实现上是通过
AbstractReferenceCountedByteBuf来实现的,我们看上面的结构图,可以看到AbstractReferenceCountedByteBuf是AbstractByteBuf的直接子类,所有具体的实现ByteBuf(堆,非堆等)都是继承自AbstractReferenceCountedByteBuf,也就是说,Netty的具体的实现ByteBuf,都是具有内存回收管理的功能的
AbstractReferenceCountedByteBuf有两个重要的成员变量:
1、AtomicIntegerFieldUpdater< AbstractReferenceCountedByteBuf> refCntUpdater
用来更新引用数,使用原子类,达到线程安全
2、volatile int refCnt = 1
用来记录引用数,保证可见性
引用+1
public ByteBuf retain() {
int refCnt;
do {
refCnt = this.refCnt;
if(refCnt == 0) {
throw new IllegalReferenceCountException(0, 1);
}
if(refCnt == 2147483647) {
throw new IllegalReferenceCountException(2147483647, 1);
}
} while(!refCntUpdater.compareAndSet(this, refCnt, refCnt + 1));
return this;
}
public ByteBuf retain() {
int refCnt;
do {
refCnt = this.refCnt;
if(refCnt == 0) {
throw new IllegalReferenceCountException(0, 1);
}
if(refCnt == 2147483647) {
throw new IllegalReferenceCountException(2147483647, 1);
}
} while(!refCntUpdater.compareAndSet(this, refCnt, refCnt + 1));
return this;
}
引用-1
public final boolean release() {
int refCnt;
do {
refCnt = this.refCnt;
if(refCnt == 0) {
throw new IllegalReferenceCountException(0, -1);
}
} while(!refCntUpdater.compareAndSet(this, refCnt, refCnt - 1));
if(refCnt == 1) {
this.deallocate();
return true;
} else {
return false;
}
}
public final boolean release() {
int refCnt;
do {
refCnt = this.refCnt;
if(refCnt == 0) {
throw new IllegalReferenceCountException(0, -1);
}
} while(!refCntUpdater.compareAndSet(this, refCnt, refCnt - 1));
if(refCnt == 1) {
this.deallocate();
return true;
} else {
return false;
}
}
可以看出,无论是增加引用还是释放引用,都是使用了CAS
refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)
1
对象时this,自身,也就是说,统计的是自身的引用数,例如对于UnPoolHeapByteBuf来说,它具有统计有多少对象引用着它,当引用数refCnt == 1时,表示此事已经没有对象引用它了,此时便调用deallocate来释放内存
---------------------
作者:JeffCoding
来源:CSDN
原文:https://blog.csdn.net/jeffleo/article/details/69230112
版权声明:本文为博主原创文章,转载请附上博文链接!
Netty ByteBuf和Nio ByteBuffer的更多相关文章
- netty byteBuf (二)
netty重新定义了byteBuf 而没使用jdk byteBuffer netty byteBuf与jdk byteBuffer的区别 (1)jdk buffer长度固定 byteBuf超过最大 ...
- Netty ByteBuf源码分析
Netty的ByteBuf是JDK中ByteBuffer的升级版,提供了NIO buffer和byte数组的抽象视图. ByteBuf的主要类集成关系: (图片来自Netty权威指南,图中有一个画错的 ...
- 对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解
此文章已同步发布在我的 segmentfault 专栏. 根据 Wiki 对 Zero-copy 的定义: "Zero-copy" describes computer opera ...
- Netty实践与NIO原理
一.阻塞IO与非阻塞IO Linux网络IO模型(5种) (1)阻塞IO模型 所有文件操作都是阻塞的,以套接字接口为例,在进程空间中调用recvfrom,系统调用直到数据包到达且被复制到应用进程缓冲区 ...
- 一步一图带你深入剖析 JDK NIO ByteBuffer 在不同字节序下的设计与实现
让我们来到微观世界重新认识 Netty 在前面 Netty 源码解析系列 <聊聊 Netty 那些事儿>中,笔者带领大家从宏观世界详细剖析了 Netty 的整个运转流程.从一个网络数据包在 ...
- 【Netty】netty学习之nio了解
[一]五种IO模型: (1)阻塞IO(2)非阻塞IO(任务提交,工作线程处理,委托线程等待工作线程处理结果的同时,也可以做其他的事情)(3)IO复用模型.(委托线程接收多个任务,将任务提交给工作线程. ...
- Netty ByteBuf(图解之 2)| 秒懂
目录 Netty ByteBuf(图解二):API 图解 源码工程 写在前面 ByteBuf 的四个逻辑部分 ByteBuf 的三个指针 ByteBuf 的三组方法 ByteBuf 的引用计数 Byt ...
- 客户端(springmvc)调用netty构建的nio服务端,获得响应后返回页面(同步响应)
后面考虑通过netty做一个真正意义的简约版RPC框架,今天先尝试通过正常调用逻辑调用netty构建的nio服务端并同步获得返回信息.为后面做铺垫 服务端实现 我们先完成服务端的逻辑,逻辑很简单,把客 ...
- 学习 java netty (一) -- java nio
前言:近期在研究java netty这个网络框架,第一篇先介绍java的nio. java nio在jdk1.4引入,事实上也算比較早的了.主要引入非堵塞io和io多路复用.内部基于reactor模式 ...
随机推荐
- 010_React-组件的生命周期详解
ReactJS 的核心思想是组件化,即按功能封装成一个一个的组件,各个组件维护自己的状态和 UI,当状态发生变化时,会自定重新渲染整个组件,多个组件一起协作共同构成了 ReactJS 应用. 为了能够 ...
- Linux中查看你的用户是否为root用户
可以使用sudo -l命令: user@fafsf:/opt/user$ sudo -l [sudo] password for user: //这里是要输入你的密码 Sorry, user user ...
- tomcat 启动慢问题
主要原因: 生成随机数的时候卡住了,导致tomcat启动不了. 是否有足够的熵来用于产生随机数,可以通过如下命令来查看 [root@oldboy tools]# cat /proc/sys/kerne ...
- MySQL 主主配置
一.准备 1.两个数据库版本最好保持一致(因为官方就是这么建议的,主要的问题就是考虑到兼容性问题) 2.连个数据库的数据保持一致,若不一致,可手动调整,比如A比B多一个库,那就将这个库导入到B库,达到 ...
- day12--装饰器
定义(如何理解装饰器):装饰器本生是闭包函数的一种应用,是指在不改变原函数的情况下为原函数添加新的功能的一个函数.它把被装饰的函数作为外层函数的参数传入装饰器,通过闭包操作后返回一个替代版函数. 遵循 ...
- 在Python程序中的进程操作,multiprocess.Process模块
在python程序中的进程操作 之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了,运行中的程序就是一个进程.所有的进程都是通过它的父进程来创建的.因此,运行起 ...
- Vue2.x源码学习笔记-Vue实例的属性和方法整理
还是先从浏览器直观的感受下实例属性和方法. 实例属性: 对应解释如下: vm._uid // 自增的id vm._isVue // 标示是vue对象,避免被observe vm._renderProx ...
- SpringBoot集成Shiro安全框架
跟着我的步骤:先运行起来再说 Spring集成Shiro的GitHub:https://github.com/yueshutong/shiro-imooc 一:导包 <!-- Shiro安全框架 ...
- [WPF]何如在Win7使用Aero2主题
1. 问题 假设我在Windows10的环境新建一个4.6的WPF项目,添加一个ComboBox,并用Blend在这个ComboBox上右键"编辑模板"->"编辑副 ...
- python-PyQuery详解
PyQuery库也是一个非常强大又灵活的网页解析库,如果你有前端开发经验的,都应该接触过jQuery,那么PyQuery就是你非常绝佳的选择,PyQuery 是 Python 仿照 jQuery 的严 ...