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

让我们来到微观世界重新认识 Netty

在前面 Netty 源码解析系列 《聊聊 Netty 那些事儿》中,笔者带领大家从宏观世界详细剖析了 Netty 的整个运转流程。从一个网络数据包在内核中的收发过程开始聊起,总体介绍了 Netty 的 IO 线程模型,后面我们围绕着这个 IO 模型又详细介绍了整个 Reactor 模型在 Netty 中的实现。

这个宏观流程包括:Reactor模型的创建,启动,运转架构,网络连接的接收和关闭,网络数据的接收和发送,利用 pipeline 对 IO 处理逻辑的编排,Netty 的优雅关闭。

Netty 的源码解析系列写到这里,笔者算是带着大家在 Netty 的宏观世界中翱翔了一圈,但笔者还是不舍得和大家说再见,于是决定在带领大家到 Netty 的微观世界中一探究竟,这个系列的目的就是想让大家从内核层面深入地搞透 Netty。

在 Netty 的微观世界系列中,笔者会为大家讲述 Netty 中的高性能组件的相关设计和实现以及应用。内容包括:

  • Netty 中的网络数据容器 ByteBuf 的整个设计体系的实现。
  • Netty 中的内存池设计与实现,在这个过程中,笔者会把 Linux 内核中内存管理子系统相关源码带大家走读一遍,让大家从内核层面到应用层面彻底搞透彻高性能内存分配的原理及其实现。
  • Netty 中用于执行海量延时任务的时间轮相关设计与实现,并与 Kafka 中的时间轮设计做出详细对比。
  • Netty 中用到的零拷贝技术在内核中的实现。
  • Netty 中用到的 MPSC (多生产者单消费者)队列的设计与实现以及应用场景。
  • Netty 中实现无锁化并发的关键组件 FastThreadLocal 的设计与实现,并详细对比 FastThreadLocal 究竟比 JDK 中 ThreadLocal 快在了哪里。
  • 理论讲完了,实践是必不可少的,最后笔者会带大家剖析 Netty 在各个著名中间件中是如何使用的,进一步加深大家对 Netty 的理解。

笔者的这个 Netty 微观世界系列会涉及大量丰富的细节描述,对于喜欢细节控的同学一定不要错过~~

写在本文开始之前…..

本文我们开始 Netty 微观世界系列第一部分的内容,聊聊 Netty 中的网络数据容器 ByteBuf ,对于 ByteBuf 我想大家一定不会陌生,它曾多次出现在前面的系列文章中,比如在《Netty如何高效接收网络数据 | 一文聊透ByteBuffer动态自适应扩缩容机制》《一文搞懂Netty发送数据全流程 | 你想知道的细节全在这里》这两篇文章中提到的 Netty 接收网络数据和发送网络数据时用到的ByteBuf。

ByteBuf 是 Netty 中的数据容器,Netty 在接收网络数据和发送网络数据时,都会首先将这些网络数据事先缓存在 ByteBuf 中,然后在将它们丢给 pipeline 处理或者发送给 Socket ,这样做的目的是防止在接收网络数据的过程中网络数据一直积压在 Socket 的接收缓冲区中使得接收缓冲区的数据越来越多,导致对端 TCP 协议中的窗口关闭(滑动窗口),影响到了整个 TCP 通信的速度。而有了 ByteBuf,我们可以先将读取的数据缓存在 ByteBuf 中,提高 TCP 的通信能力。

而在 Netty 发送数据的时候,也可以事先将数据缓存在 ByteBuf 中,如果 Socket 发送缓冲区已满变为不可写状态时,由于数据我们已经缓存在 ByteBuf 中了,用户的发送线程不需要阻塞等待,当 Socket 发送缓冲区再次变得可写时,Netty 会将 ByteBuf 中的数据写入到 Socket 中。这也是 Netty 实现异步发送数据的核心所在。

而 Netty 中的 ByteBuf 底层依赖了JDK NIO 中的 ByteBuffer 。众所周知 JDK NIO 中的 ByteBuffer 设计的非常复杂而且提供的相关 API 使用起来也很反人类,易用性不是很好,所以 Netty 的 ByteBuf 针对 JDK NIO ByteBuffer 进行了优化,再此基础上重新设计出了一套简洁易用的 API 出来。

熟悉笔者写作风格的读者朋友都知道,笔者一向是喜欢把技术的脉络给大家铺展开来讲解,一层一层地介绍技术的演变过程,力求给大家清晰地展现出整个技术的全貌。通过技术的演变过程,我们不仅可以知道这个技术点最初的样貌,它的优缺点是什么?瓶颈是什么?我们还可以针对这些缺点和瓶颈触发自己的思考,如何优化?如何演变?通过这个过程的洗礼,我们才能够对现有技术理解的清晰透彻。

根据这个思路,在介绍 Netty 的 ByteBuf 设计之前,笔者想专门用一篇文章来为大家介绍下 JDK NIO Buffer 的设计,看一下 NIO ByteBuffer 是如何设计的,它有哪些缺点。针对这些缺点,Netty 又是如何优化的。彻底理解 Netty 数据载体 ByteBuf 的前世今生。

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

1. JDK NIO 中的 Buffer

在 NIO 没有出现之前,Java 传统的 IO 操作都是通过流的形式实现的(包括网络 IO 和文件 IO ),也就是我们常见的输入流 InputStream 和输出流 OutputStream。

但是 Java 传统 IO 的 InputStream 和 OutputStream 的相关操作全部都是阻塞的,比如我们使用 InputStream 的 read 方法从流中读取数据时,如果此时流中没有数据,那么用户线程就必须阻塞等待。

还有一点就是传统的这些输入输出流在处理字节流的时候一次只能处理一个字节,这样在处理网络 IO 的时候读取 Socket 缓冲区中的数据效率就会很低,而且在操作字节流的时候只能线性的处理流中的字节,不能来回移动字节流中的数据。这样导致我们在处理字节流中的数据的时候就显得不是很灵活。

所以综上所述,Java 传统 IO 是面向流的,流的处理是单向,阻塞的,而且无论是从输入流中读取数据还是向输出流中写入数据都是一个字节一个字节来处理的。通常都是从输入流中边读取数据边处理数据,这样 IO 处理效率就会很低,

基于上述原因,JDK1.4 引入了 NIO,而 NIO 是面向 Buffer 的,在处理 IO 操作的时候,会一次性将 Channel 中的数据读取到 Buffer 中然后在做后续处理,向 Channel 中写入数据也是一样,也是需要一个 Buffer 做中转,然后将 Buffer 中的数据批量写入 Channel 中。这样一来我们可以利用 Buffer 将里面的字节数据来回移动并根据我们想要的处理方式灵活处理。

除此之外,Nio Buffer 还提供了堆外的直接内存和内存映射相关的访问方式,来避免内存之间的来回拷贝,所以即使在传统 IO 中用到了 BufferedInputStream 也还是没办法和 Nio Buffer 相匹敌。

那么接下来就让我们正式进入JDK NIO Buffer 如何设计与实现的相关主题

2. NIO 对 Buffer 的顶层抽象

JDK NIO 提供的 Buffer 其实本质上是一块内存,大家可以把它简单想象成一个数组,JDK 将这块内存在语言层面封装成了 Buffer 的形式,我们可以通过 Buffer 对这块内存进行读取或者写入数据,以及执行各种骚操作。

如下图中所示,Buffer 类是JDK NIO 定义的一个顶层抽象类,对于缓冲区的所有基本操作和基础属性全部定义在顶层 Buffer 类中,在 Java 中一共有八种基本类型,JDK NIO 也为这八种基本类型分别提供了其对应的 Buffer 类,大家可以把这些 Buffer 类当做成对应基础类型的数组,我们可以利用这些基础类型相关的 Buffer 类对数组进行各种操作。

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

在为大家解析具体的缓冲区实现之前,我们先来看下这个缓冲区的顶层抽象类 Buffer 中到底定义规范了哪些抽象操作,具有哪些属性,这些属性分别是用来干什么的?先带大家从总体上认识一下JDK NIO 中的 Buffer 设计。

2.1 Buffer 中的属性

public abstract class Buffer {

    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

             .............

}

首先我们先来介绍下 Buffer 中最重要的这三个属性,后面即将介绍的关于 Buffer 的各种骚操作均依赖于这三个属性的动态变化。

一步一图带你深入剖析 JDK NIO ByteBuffer 在不同字节序下的设计与实现
  • capacity:这个很好理解,它规定了整个 Buffer 的容量,具体可以容纳多少个元素。capacity 指针之前的元素均是 Buffer 可操作的空间。
  • position:用于指向 Buffer 中下一个可操作性的元素,初始值为 0。在 Buffer 的写模式下,position 指针用于指向下一个可写位置。在读模式下,position 指针指向下一个可读位置。
  • limit:表示 Buffer 可操作元素的上限。什么意思呢?比如在 Buffer 的写模式下,可写元素的上限就是 Buffer 的整体容量也就是 capacity ,capacity – 1 即为 Buffer 最后一个可写位置。在读模式下,Buffer 中可读元素的上限即为上一次 Buffer 在写模式下最后一个写入元素的位置。也就是上一次写模式中的 position。
  • mark:用于标记 Buffer 当前 position 的位置。这个字段在我们对网络数据包解码的时候非常有用,在我们使用 TCP 协议进行网络数据传输的时候经常会出现粘包拆包的现象,所以为了应对粘包拆包的问题,在解码之前都需要先调用
    mark 方法将 Buffer 的当前 position 指针保存至 mark 属性中,如果 Buffer 中的数据足够我们解码为一个完整的包,我们就执行解码操作。如果 Buffer 中的数据不够我们解码为一个完整的包(也就是半包),我们就调用 reset 方法,将 position 还原到原来的位置,等待剩下的网络数据到来。

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

在我们理解了 Buffer 中这几个重要属性的含义之后,接下来我们就来看一看 JDK NIO 在 Buffer 顶层设计类中定义规范的那些抽象操作。

2.2 Buffer 中定义的核心抽象操作

本小节中介绍的这几个关于 Buffer 的核心操作均是基于上小节中介绍的那些核心指针的动态调整实现的。

2.2.1 Buffer 的构造

构造 Buffer 的主要逻辑就是根据用户指定的参数来初始化 Buffer 中的这四个重要属性:mark,position,limit,capacity。它们之间的关系为:mark

Original: https://www.cnblogs.com/binlovetech/p/16575557.html
Author: bin的技术小屋
Title: 一步一图带你深入剖析 JDK NIO ByteBuffer 在不同字节序下的设计与实现

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/577926/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • KVM http网络加载镜像报错(mount: wrong fs type, bad option, bad superblock on /dev/loop0)

    curl: (23) Failed writing body (7818 != 16384)loop: module loadeddracut-initqueue[579]: mo…

    Linux 2023年6月6日
    0150
  • map_set使用说明

    map_set使用说明 map的底层结构大致是一个哈希表,set的底层结构大致是一个红黑树 不代表全部! set #include"map_set.h" //s…

    Linux 2023年6月13日
    097
  • CSRankings: Computer Science Rankings

    CSRankings is a metrics-based ranking of top computer science institutions around the worl…

    Linux 2023年6月13日
    0125
  • Java动态脚本Groovy读取配置文件

    前言:请各大网友尊重本人原创知识分享,谨记本人博客: 南国以南i 核心涉及: @Value:作用是通过注解将常量、配置文件中的值、其他bean的属性值注入到变量中,作为变量的初始值…

    Linux 2023年6月14日
    081
  • 职场最讨厌的人,没有之一

    人物背景: 姓名:春绿,性别:未知,年龄:不详,工龄:菜鸟,人物特点:爱管闲事,管不住自己的嘴,情商约等于0.000001 人物故事: 1、领导给小明安排了一个工作,被春绿听到了,…

    Linux 2023年6月13日
    0104
  • Java — 反射

    程序在运行中也可以获取类的变量和方法信息,并通过获取到的信息来创建对象。程序不必再编译期就完成确定,在运行期仍然可以扩展。 示例:学生类 public class Student …

    Linux 2023年6月8日
    0139
  • docker redis启动将配置文件挂载在数据卷(volume)中

    关键词 [     "linux",     "docker",     "volume",     "red…

    Linux 2023年5月28日
    089
  • samba服务设置与访问共享文件夹

    samba服务设置与访问共享文件夹 linux设置文件夹共享 windows连接共享文件夹(运行->//IP/route) linux连接共享文件夹 1、基本服务安装与配置 …

    Linux 2023年6月14日
    094
  • .NET 6上的WebView2体验

    上次说为了不想在web端登录博客园,我想着还是继续使用 MarkWord编写博客,不过在使用的过程中,如果markdown文件的目录中有中文的话,Markdown预览就不能够显示粘…

    Linux 2023年6月6日
    0114
  • 实验2:Open vSwitch虚拟交换机实践

    实验2:Open vSwitch虚拟交换机实践 一、实验目的 能够对Open vSwitch进行基本操作; 能够通过命令行终端使用OVS命令操作Open vSwitch交换机,管理…

    Linux 2023年6月7日
    0126
  • OpenResty入门

    OpenResty介绍 OpenResty通过汇聚各种设计精良的 Nginx模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web …

    Linux 2023年6月8日
    098
  • go操作redis

    package main import ( "fmt" "github.com/gomodule/redigo/redis" ) func …

    Linux 2023年5月28日
    081
  • VR一体机如何退出FFBM(QFIL)

    前文介绍了通过fastboot命令擦除misc分区,从而退出FFBM的方法。这个方法比较简便,但有不灵的时候,fastboot erase misc命令执行失败,如下图所示。 er…

    Linux 2023年6月7日
    0111
  • linux学习之shell脚本

    【实验目的】‍ ‌ 通过本实验练习,使学生了解常用SHELL的编程特点,掌握SHELL 程序设计的基础知识。对SHELL程序流程控制、SHELL程序的运行方式、bash程序的调试方…

    Linux 2023年5月27日
    0134
  • DSTAT, Versatile resource statistics tool, 多功能资源统计工具;

    之前用到的一个IO监控工具,今天要使用,却忘记了名字,记录一下: dstat命令是一个用来替换vmstat、iostat、netstat、nfsstat和ifstat这些命令的工具…

    Linux 2023年6月13日
    090
  • vscode配置指南,美化技巧

    "workbench.colorCustomizations": { "editor.selectionBackground": &quot…

    Linux 2023年6月14日
    093
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球