JDK又在写Bug!告诉你为何Java NIO的ByteBuffer这么垃圾!(上)

简介: JDK又在写Bug!告诉你为何Java NIO的ByteBuffer这么垃圾!

网络数据的基本单位永远是 byte(字节)。Java NIO 提供 ByteBuffer 作为字节的容器,但该类过于复杂,有点难用。


ByteBuf是Netty当中的最重要的工具类,它与JDK的ByteBuffer原理基本上相同,也分为堆内与堆外俩种类型,但是ByteBuf做了极大的优化,具有更简单的API,更多的工具方法和优秀的内存池设计。



1 API

Netty 的数据处理 API 通过两个组件暴露——抽象类ByteBuf 和 接口 ByteBufHolder。

ByteBuf API 的优点:



它可以被用户自定义的缓冲区类型扩展

通过内置的复合缓冲区类型实现了透明的零拷贝;

容量可以按需增长(类似于 JDK 的 StringBuilder)

在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法

读和写使用了不同的索引

支持方法的链式调用

支持引用计数

支持池化



其他类可用于管理 ByteBuf 实例的分配,以及执行各种针对于数据容器本身和它所持有的数据的操作。



2 Netty 的数据容器

所有网络通信最终都是基于底层的字节流传输,因此高效、方便、易用的数据接口是迷人的,而 Netty 的 ByteBuf 生而为满足这些需求。



2.1 工作原理

ByteBuf 维护俩不同索引:一个用于读取,一个用于写入:



  • 从 ByteBuf 读取时,其 readerIndex 将会被递增已经被读取的字节数
  • 当写入 ByteBuf 时,writerIndex 也会被递增
  • 一个读索引和写索引都设置为 0 的 16 字节 ByteBuf

image.png

image.png



这些索引两两之间有什么关系呢?

若打算读取字节直到 readerIndex == writerIndex,会发生啥?此时,将会到达“可读取的”数据的末尾。类似试图读取超出数组末尾的数据一样,试图读取超出该点的数据也会抛 IndexOutOfBoundsException


image.png



  • read、write 开头的 ByteBuf 方法,会推进对应索引
  • set、get 开头的操作则不会。后面的这些方法将在作为一个参数传入的一个相对索引上执行操作


可指定 ByteBuf 的最大容量。试图移动写索引(即 writerIndex)超过这个值将会触

发一个异常。(默认限制 Integer.MAX_VALUE。)



内存池化

非池化的堆内与堆外的 ByteBuf 示意图


image.png

ByteBuf heapBuffer = UnpooledByteBufAllocator.DEFAULT.heapBuffer(10);
ByteBuf directBuffer = UnpooledByteBufAllocator.DEFAULT.directBuffer(10);

注意要手动将GC 无法控制的非堆内存的空间释放:


池化的堆内与堆外的 ByteBuf 示意图


image.png



字节级操作

派生缓冲区



派生缓冲区为 ByteBuf 提供了以专门的方式来呈现其内容的视图。这类视图通过以下方法创建:

  • Unpooled.unmodifiableBuffer(…)
  • order(ByteOrder)
  • readSlice(int)


这些方法都将返回一个新的 ByteBuf 实例,但都具有自己独立的读、写和标记索引。

其内部存储和 JDK 的 ByteBuffer 一样,都是共享的。所以派生缓冲区的创建成本很低,但同时也表明若你修改了它的内容,也会同时修改对应源实例!


slice、slice(int, int)、retainedSlice、retainedSlice(int, int)


返回此缓冲区的可读字节的一部分。

此方法与buf.slice(buf.readerIndex(), buf.readableBytes())相同。

该方法不会调用retain(),引用计数不会增加。

retainedSlice系列方法调用类似slice().retain(),但此方法可能返回产生较少垃圾的缓冲区实现。


image.png



duplicate、retainedDuplicate


image.png


返回一个共享该缓冲区整个区域的缓冲区。

此方法不会修改此缓冲区的readerIndex或writerIndex


image.png


读取器和写入器标记将不会重复。

duplicate不会调用retain(),不会增加引用计数,而retainedDuplicate会。

目录
相关文章
|
14天前
|
分布式计算 DataWorks Java
DataWorks操作报错合集之在使用MaxCompute的Java SDK创建函数时,出现找不到文件资源的情况,是BUG吗
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
26 0
|
3天前
|
Java 开发框架 XML
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
JDK、JRE、Java SE、Java EE和Java ME有什么区别?
|
4天前
|
Java
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
【JAVA进阶篇教学】第四篇:JDK8中函数式接口
|
4天前
|
Java API
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
|
4天前
|
Java
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
【JAVA进阶篇教学】第二篇:JDK8中Lambda表达式
|
4天前
|
Java API
【JAVA进阶篇教学】第一篇:JDK8介绍
【JAVA进阶篇教学】第一篇:JDK8介绍
|
5天前
|
Java
JDK环境下利用记事本对java文件进行运行编译
JDK环境下利用记事本对java文件进行运行编译
15 0
|
5天前
|
IDE Java 应用服务中间件
JDK1.6.0+Tomcat6.0的安装配置(配置JAVA环境)
JDK1.6.0+Tomcat6.0的安装配置(配置JAVA环境)
18 1
|
6天前
|
Java 开发工具
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
2023全网最详细的银河麒麟操作系统,Java运行环境【jdk】安装
|
7天前
|
Java 编译器 对象存储
java一分钟之Java入门:认识JDK与JVM
【5月更文挑战第7天】本文介绍了Java编程的基础——JDK和JVM。JDK是包含编译器、运行时环境、类库等的开发工具包,而JVM是Java平台的核心,负责执行字节码并实现跨平台运行。常见问题包括版本不匹配、环境变量配置错误、内存溢出和线程死锁。解决办法包括选择合适JDK版本、正确配置环境变量、调整JVM内存参数和避免线程死锁。通过代码示例展示了JVM内存管理和基本Java程序结构,帮助初学者更好地理解JDK和JVM在Java编程中的作用。
20 0