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

大家都在看

  • keil使用汇总

    ​ 一:参考博客 参考的教程如下: 首先必须声明的一点是所有的博客都来自于博主strongerHuang,我只是为了记录方便copy下来,如有侵权,请联系删除帖子。链接地址如下:h…

    Linux 2023年6月13日
    0135
  • Linux动静分离与Rewrite

    一、动静分离 1.1 单台机器动静分离 1、创建NFS挂载点(NFS服务端) mkdir /static vim /etc/exports /static 172.16.1.0/2…

    Linux 2023年6月14日
    088
  • 为知笔记迁移到印象笔记-从入门到放弃

    最新进展 已经放弃了,目前正在逐步把笔记迁移到本地,用icloud来同步。 为什么放弃迁移? 没有找到好的迁移方案,迁移过去文档不方便查找和使用 为什么放弃印象笔记? 1.主要使用…

    Linux 2023年6月14日
    092
  • Redis快速度特性及为什么支持多线程及应用场景

    转载请注明出处: 1.Redis 访问速度快特性 正常情况下,Redis执行命令的速度非常快,官方给出的数字是读写性能可以达到10万/秒,当然这也取决于机器的性能;Redis使用了…

    Linux 2023年5月28日
    0101
  • 二、Java分布式(第二章)—-Demo

    这一章简单搭建一个分布式服务: 1、Dubbo简介:Dubbo 是一个分布式服务框架,是阿里巴巴开源项目。 Dubbo 致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA…

    Linux 2023年6月7日
    0123
  • 关于docker中容器可以Ping通外网,真机无法Ping通容器的问题

    首先我们要知道整体的框架结构,docker是我们安装在centos7上的,而centos7是安装在vmware上。其中docker中还有若干容器运行。 整体框架图如下: 我们将它分…

    Linux 2023年5月27日
    0186
  • linux系统引导过程

    linux系统引导过程 linux-0.11引导时,将依次运行BIOS程序、bootsect.s、setup.s和head.s,完成引导过程后进入到main函数运行。BIOS完成硬…

    Linux 2023年6月13日
    072
  • Spring常见异常说明

    文章要点 Spring bean 的声明方式 Spring bean 的注入规则 Spring bean 的依赖查找规则 Spring bean 的名称定义方式和默认名称规则 XX…

    Linux 2023年6月6日
    099
  • redis中setbit的用法

    原文地址:http://www.zhihu.com/question/27672245 在redis中,存储的字符串都是以二级制的进行存在的。举例:设置一个 key-value ,…

    Linux 2023年5月28日
    096
  • Question09-查询学过编号为”01″并且也学过编号为”02″的课程的同学的信息

    * — 学过01,且学过02 — 学过01 SELECT sc.SID FROM SC sc WHERE sc.CID = ’01’; — 学过02 SELECT sc.SI…

    Linux 2023年6月7日
    094
  • 解决Ubuntu(20.04)开机、关机、重启慢,有光标闪烁问题

    1. 问题描述 在开关机或重启时,等待时间很长,大约1分30秒,光标闪烁。 [En] When switching on and off, or rebooting, the wa…

    Linux 2023年5月27日
    076
  • Linux之Keepalived高可用

    一、高可用介绍 一般是指2台机器启动着完全相同的业务系统,当有一台机器down机了,另外一台服务器就能快速的接管,对于访问的用户是无感知的。 硬件通常使用:F5 软件通常使用:Ke…

    Linux 2023年5月27日
    0115
  • AWS修改RDS时区

    查看 RDS 当前时区 默认情况下,AWS 的 RDS 采用的是 UTC 时间。而我们地区一般位于东八区,因此我们本地的时间是 UTC+8。 连接到 RDS 上,查询当前实例的时区…

    Linux 2023年6月7日
    0103
  • WSL2+Docker+IDEA一站式开发调试

    WSL2+Docker+IDEA一站式开发调试 前言 ​ 我们知道,Docker是一个容器引擎;对于开发者来说,使用Dokcer容器部署各种开发需要的中间件(比如myql、redi…

    Linux 2023年6月7日
    0119
  • 【Python】【爬虫】【问题解决方案记录】调试输出存在数据,print在控制台确丢失数据

    调试输出存在数据,print在控制台确丢失数据 如下图,调试可以看到数据是完整的 但是print输出的,恰好丢失了中间的一大堆数据。对,下图打问号的地方应该是小说才对。 看代码可能…

    Linux 2023年6月14日
    074
  • 2021年度总结 2022年度规划

    2021年 计划 1、学习更多的知识😁 2、学习408的知识,至少能熟悉计算机组成原理、操作系统、计算机网络、算法这几个的联系,区别等。😁 3、整理408的知识到博客上。 (一篇未…

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