参考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的更多相关文章

  1. netty byteBuf (二)

    netty重新定义了byteBuf 而没使用jdk byteBuffer netty byteBuf与jdk  byteBuffer的区别 (1)jdk buffer长度固定  byteBuf超过最大 ...

  2. Netty ByteBuf源码分析

    Netty的ByteBuf是JDK中ByteBuffer的升级版,提供了NIO buffer和byte数组的抽象视图. ByteBuf的主要类集成关系: (图片来自Netty权威指南,图中有一个画错的 ...

  3. 对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解

    此文章已同步发布在我的 segmentfault 专栏. 根据 Wiki 对 Zero-copy 的定义: "Zero-copy" describes computer opera ...

  4. Netty实践与NIO原理

    一.阻塞IO与非阻塞IO Linux网络IO模型(5种) (1)阻塞IO模型 所有文件操作都是阻塞的,以套接字接口为例,在进程空间中调用recvfrom,系统调用直到数据包到达且被复制到应用进程缓冲区 ...

  5. 一步一图带你深入剖析 JDK NIO ByteBuffer 在不同字节序下的设计与实现

    让我们来到微观世界重新认识 Netty 在前面 Netty 源码解析系列 <聊聊 Netty 那些事儿>中,笔者带领大家从宏观世界详细剖析了 Netty 的整个运转流程.从一个网络数据包在 ...

  6. 【Netty】netty学习之nio了解

    [一]五种IO模型: (1)阻塞IO(2)非阻塞IO(任务提交,工作线程处理,委托线程等待工作线程处理结果的同时,也可以做其他的事情)(3)IO复用模型.(委托线程接收多个任务,将任务提交给工作线程. ...

  7. Netty ByteBuf(图解之 2)| 秒懂

    目录 Netty ByteBuf(图解二):API 图解 源码工程 写在前面 ByteBuf 的四个逻辑部分 ByteBuf 的三个指针 ByteBuf 的三组方法 ByteBuf 的引用计数 Byt ...

  8. 客户端(springmvc)调用netty构建的nio服务端,获得响应后返回页面(同步响应)

    后面考虑通过netty做一个真正意义的简约版RPC框架,今天先尝试通过正常调用逻辑调用netty构建的nio服务端并同步获得返回信息.为后面做铺垫 服务端实现 我们先完成服务端的逻辑,逻辑很简单,把客 ...

  9. 学习 java netty (一) -- java nio

    前言:近期在研究java netty这个网络框架,第一篇先介绍java的nio. java nio在jdk1.4引入,事实上也算比較早的了.主要引入非堵塞io和io多路复用.内部基于reactor模式 ...

随机推荐

  1. IOT,笔记:avrdude: ser_open(): can't open device "\\.\COM3": 系统找不到指定的文件。

    1.下载驱动:https://www.arduino.cc/ 下载后解压 2.UNO板子以及驱动的相关设置 将UNO板子用数据线连接到电脑上,设置驱动: 打开设备管理器----->找到端口--- ...

  2. SpringBoot的简单登陆开发例子

    1:这个例子用spirngboot整合mybatis,jdbc等技术开发的 2:步骤 2.1:新建一个工程 主要的两个步骤已经贴图了,第二张图是直接在pom.xml文件中加入依赖 2.2:新建完项目, ...

  3. 【转】iOS弹幕库OCBarrage-如何hold住每秒5000条巨量弹幕

    最近公司做新需求, 原来用的老弹幕库, 已经无法满足需要. 迫不得已自己写了一套弹幕库OCBarrage. 这套弹幕库轻量, 可拓展, 高度自定义, 超高性能, 简单易上手. 无论哪家公司软件的性能绝 ...

  4. PostgreSQL安装和使用

    青岛OJ系统用的关系型数据库是PostgreSQL,为此对PostgreSQL大致了解下. 今天的主要话题围绕下面两个方面: PostgreSQL安装 PostgreSQL使用 一.PostgreSQ ...

  5. keepalived+lvs子网掩码造成VIP切换故障 + vrrp_script+track_script

    keepalived+lvs子网掩码造成VIP切换故障 架构:keepalived+lvs ,前端调度器是双主模型 现象:keepalived手动停掉一台,但是虚拟IP不会切换 整体网络是24位 VI ...

  6. Unity热更新学习(二) —— ToLua c#与lua的相互调用

    tolua 下载地址:http://www.ulua.org/index.html c#调用lua的方法,tolua的官方例子提供了很多种.我初步学了一种在做项目使用的方法.通过DoFile方法执行l ...

  7. X86-64寄存器和栈帧--牛掰降解汇编函数寄存器相关操作

    X86-64寄存器和栈帧 概要 说到x86-64,总不免要说说AMD的牛逼,x86-64是x86系列中集大成者,继承了向后兼容的优良传统,最早由AMD公司提出,代号AMD64:正是由于能向后兼容,AM ...

  8. redis为什么这么火该怎么用

    最近一些人在介绍方案时,经常会出现redis这个词,于是很多小伙伴百度完redis也就觉得它是一个缓存,然后项目里面把数据丢进去完事,甚至有例如将实体属性拆分塞进redis hash里面的奇怪用法等等 ...

  9. jquery tooltip

    这是个加了点淡入淡出效果的顶部tooltip控件,会自动消失 用法: <head> <title></title> <link href="base ...

  10. VC++全屏

    Win32类型的全屏代码: 1. 去掉menu ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = s ...