一步一图带你深入剖析 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)

大家都在看

  • 【电台谈心】2022罗翔老师回答毕业生的4个问题

    罗翔老师:谢谢这位同学的问题。我回想起自己找工作的经历,其实也是 一地鸡毛。本科毕业的时候,也没有多少招聘会。我只有一个单位,一个单位地去找。少数几家单位让你进去了,聊了聊又觉得你…

    Linux 2023年6月13日
    0106
  • 微服务的性能监控、压测和调优(转载自知乎:阿里自动化测试群)

    一、何为压力测试 性能压测是什么:就是考察当前 软件和 硬件环境下,系统所能承受的 &amp…

    Linux 2023年6月8日
    097
  • algorithm 头文件参考

    定义执行算法的 C++ 标准库容器模板函数。 该 <algorithm></algorithm> 库还使用该 #include <initialize…

    Linux 2023年6月7日
    0102
  • SpringBoot的文件上传&下载

    前言:不多BB直接上代码 文件上传 pom依赖添加commons-io <!– 上传/下载jar https://mvnrepository.com/artifact/co…

    Linux 2023年6月14日
    097
  • .Net MVC实现全局异常捕捉返回通用异常页面的一种方式

    阅文时长 | 0.54分钟字数统计 | 876字符主要内容 | 1、引言&背景 2、部分通用设计代码 3、声明与参考资料『.Net MVC实现全局异常捕捉返回通用异常页面的…

    Linux 2023年6月13日
    094
  • linux 文件查找详解

    文件查找 介绍:在文件系统上查找符合条件的文件。 文件查找: 非实时查找(数据库查找):locate 实时查找:find 1、locate locate 查询系统上预建的文件索引数…

    Linux 2023年6月7日
    093
  • 数字数组

    3、【剑指Offer学习】【面试题03:找出数组中重复的数字】 4、【剑指Offer学习】【面试题04:二维数组中的查找】 11、【剑指Offer学习】【面试题11:旋转数组的最小…

    Linux 2023年6月13日
    0116
  • redis的 分布式锁 golang/erlang 简单实现

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% import (% “redigo/redis”% ) %…

    Linux 2023年5月28日
    0101
  • 【Leetcode】768. 最多能完成排序的块 II

    arr是一个可能包含重复元素的整数数组,我们将这个数组分割成几个”块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。 …

    Linux 2023年6月6日
    088
  • Golang 实现 Redis(5): 使用跳表实现 SortedSet

    本文是使用 golang 实现 redis 系列的第五篇, 将介绍如何使用跳表实现有序集合(SortedSet)的相关功能。 跳表(skiplist) 是 Redis 中 Sort…

    Linux 2023年5月28日
    087
  • RAID磁盘阵列

    RAID磁盘阵列 *本章重点:了解各RAID级别的原理优缺点及常用级别实现,企业中厂商大多提供了硬RAID方案。 1、什么是RAID? “RAID”一词是由…

    Linux 2023年6月7日
    087
  • Redis 主从复制

    Redis主从复制的原理 当建立主从关系时,slave配置slaveof 当redis生成dump.rdb文件时,工作过程如下 redis主进程fork一个子进程 fork出来的子…

    Linux 2023年5月28日
    087
  • 【设计模式】Java设计模式-享元模式

    Java设计模式 – 享元模式 😄 不断学习才是王道🔥 继续踏上学习之路,学之分享笔记👊 总有一天我也能像各位大佬一样🏆原创作品,更多关注我CSDN: 一个有梦有戏的人…

    Linux 2023年6月6日
    0122
  • Centos7下载及安装

    Centos7下载及安装 1.下载虚拟机 虚拟机下载地址: https://www.vmware.com 或者 360一键安装(推荐) 2.在虚拟机上安装Centos7 2.1.通…

    Linux 2023年5月27日
    084
  • Redis从入门到精通:初级篇

    原文链接:http://www.cnblogs.com/xrq730/p/8890896.html,转载请注明出处,谢谢 Redis从入门到精通:初级篇 平时陆陆续续看了不少Red…

    Linux 2023年5月28日
    082
  • [20211215]提示precompute_subquery补充.txt

    [20211215]提示precompute_subquery补充.txt –//前几天测试precompute_subquery,我仔细想一下好像以前看书或者别人的b…

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