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会。

目录
相关文章
|
3月前
|
安全 Oracle Java
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
308 0
JAVA高级开发必备·卓伊凡详细JDK、JRE、JVM与Java生态深度解析-形象比喻系统理解-优雅草卓伊凡
|
4月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
540 1
|
4月前
|
Oracle Java 关系型数据库
新手必看:Java 开发环境搭建之 JDK 与 Maven
本文分享了 Java 学习中 JDK 安装配置与 Maven 使用的入门知识,涵盖 JDK 下载安装、环境变量设置、Maven 安装配置及本地仓库与镜像设置,帮助新手快速搭建 Java 开发环境。
473 0
|
5月前
|
安全 Java API
Java最新技术(JDK 11+) 及以上 Java 最新技术之集合框架实操应用详解
本示例基于Java最新技术(JDK 11+),涵盖集合框架的核心功能,结合Java 8+特性(如Stream API、Lambda表达式)与并发编程最佳实践。内容包括:List操作(初始化、Lambda过滤、Stream处理)、Map操作(流式过滤、ConcurrentHashMap原子操作、并行流)、Set操作(TreeSet排序、CopyOnWriteArraySet并发安全)、Queue/Deque操作(优先队列、双端队列)以及高级聚合操作(集合转换、分组统计、平均值计算)。 [代码下载](https://pan.quark.cn/s/14fcf913bae6)
122 4
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
245 53
|
9月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
295 0
探索Java动态代理的奥秘:JDK vs CGLIB
|
10月前
|
算法 Java 编译器
深入理解 Java JDK —— 让我们从基础到进阶
JDK(Java Development Kit)是 Java 开发的核心工具包,包含编译、运行和调试 Java 程序所需的所有工具和库。它主要由 JVM(Java 虚拟机)、JRE(Java 运行时环境)和 Java 核心类库组成。JVM 是跨平台运行的基础,负责字节码的加载、执行和内存管理;JRE 提供运行 Java 应用的环境;核心类库则提供了丰富的 API 支持。通过编写、编译和运行一个简单的 Java 程序,可以深入理解 JDK 的工作原理。此外,JDK 还提供了 JIT 编译、垃圾回收优化和并发工具包等高级功能,帮助开发者提高程序性能和稳定性。
1216 10
|
9月前
|
缓存 网络协议 Java
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
292 0
|
11月前
|
安全 Java 编译器
一个 Bug JDK 居然改了十年?
你敢相信么一个简单的Bug,JDK 居然花了十年时间才修改完成。赶快来看看到底是个什么样的 Bug?
131 1
一个 Bug JDK 居然改了十年?