Java 与零拷贝
零拷贝是由操作系统实现的,使用 Java 中的零拷贝抽象类库在支持零拷贝的操作系统上运行才会实现零拷贝,如果在不支持零拷贝的操作系统上运行,并不会提供零拷贝的功能。
简述内核态和用户态
Linux 的体系结构分为内核态(内核空间)和用户态(用户空间),我们知道一台计算器拥有 CPU、网卡、内存和磁盘等硬件资源,内核态相当于 Linux Core,它是一种特殊的软件程序,也可以看成操作系统本身,它控制着计算机的所有硬件资源,并给用户态的进程分配所需的资源,用户态的进程不能直接访问硬件资源,它需要通过内核态体提供的接口来间接操作硬件资源。两者的关系图如下:
JVM 就是一个用户态进程。
Linux 的 IO 发展史
传统 IO
在 Java 程序中,你要从磁盘读取文件,然后将文件发送到网络中,在这个场景下,看看传统 IO 数据在操作系统中的流转情况如下:
- 开发人员调用 InputStream.read() 方法,InputStream.read() 底层调用了操作系统的 read() 接口来从磁盘读取数据,此时发生了一次上下文切换(用户态->内核态),操作系统将数据从磁盘拷贝到 read buffer,此时发生了一次数据拷贝。总共发生了一次上下文切换和一次数据拷贝。
- 操作系统将数据从 read buffer 拷贝到应用进程(JVM),即操作系统 read() 接口的返回,此时发生了一次数据拷贝和一次上下文切换(内核态->用户态),总共发生了一次上下文切换和一次数据拷贝。
- 开发人员调用 SocketOutputStream.write() 方法将数据发送到网络中,数据被从应用进程(JVM)拷贝到内核态的 Socket buffer 中,此时发生了一次数据拷贝一次上下文切换(用户态->内核态)此时在用户态,总共发生了一次上下文切换和一次数据拷贝。
- 内核态(操作系统)调用底层接口将数据从 Socket buffer 中拷贝到网络接口中,然后底层接口返回写入的结果(写入字节数等)到应用进程的方法 SocketOutputStream.write(),此时发生了一次数据拷贝和一次下文切换(内核态->用户态),总共发生了一次上下文切换和一次数据拷贝。
整个流程可以分为四步,总共需要经过四次数据拷贝和四次上下文切换。其中从硬件到内核态的拷贝称为 DMA copy,它使用了 DMA(Direct Memory Access,直接内存存取)控制器,DMA 的引入可以减少 CPU 的负担,现代磁盘基本都支持 DMA 了,从内核态到用户态的拷贝称为 CPU 拷贝,它需要 CPU 来进行拷贝,而零拷贝针对的是 CPU copy,即在整个 IO 过程中将 CPU copy 将为 0 次就叫做零拷贝,而 DMA copy 是不可避免的。
Linux 操作系统为了提升 IO 的速度,对 IO 做了一些系列的优化,其中就是以减少 CPU copy 和上下文切换为开发目的的。
mmap 内存映射
最先出现的是 mmap 内存映射,使用它之后整个 IO 流程如下:
mmap 使用了虚拟内存技术,即内核态和用户态不直接操作物理内存,而是操作虚拟内存,虚拟内存映射到物理内存,在 mmap 中将内核态的虚拟内存和用户态的虚拟内存映射到了同一块物理内存中,这样数据在被拷贝到内核态之后就不需要再拷贝到用户态了,用户态通过虚拟内存来和内核态操作同一块内存,整个流程如下:
- 开发人员调用 InputStream.read() 方法,InputStream.read() 底层调用了操作系统的 read() 接口来从磁盘读取数据,此时发生了一次上下文切换(用户态->内核态),操作系统将数据从磁盘拷贝到 read buffer,此时发生了一次数据拷贝,总共发生了一次上下文切换和一次数据拷贝。
- InputStream.read() 方法返回,此时发生了上下文切换(内核态->用户态),但是数据不需要再拷贝到用户态,用户态中的应用进程(JVM)通过虚拟内存技术和内核态共用一块物理内存,用户态对内存的操作会直接反映到内核态,总共发生了一次上下文切换。
- 开发人员调用 SocketOutputStream.write() 准备发送数据到网络中,此时发生了一次上下文切换(用户态->内核态),然后内核态将与用户态共享的那块内存拷贝到内核态的 socket buffer 中,总共发生了一次上下文切换和一次数据拷贝。
- 内核态将数据从 socket buffer 中拷贝到网络接口中,然后 SocketOutputStream.write() 方法返回写入结果,此时发生了一次上下文切换(内核态->用户态),总共发生了一次上下文切换和一次数据拷贝。
整个流程分为了四步,总共需要经过三次数据拷贝和四次上下文切换,,其中一次 CPU copy 和 两次 DMA copy。和传统 IO 相比减少了一次 CPU copy,提高了 IO 的性能以及减少了 CPU 的负载。
sendfile
Linux 2.1 出现了 sendfile 技术,使用它之后整个 IO 流程如下:
sendfile 和 mmap 有点类似,相比 mmap,它取消了内存映射的部分,这也导致了用户态的进程无法操作要发送的数据(磁盘文件),但是在不需要操作数据的场景中比 mmap 的性能更好。整个流程如下:
- 开发人员调用 FileChannel.transferTo() 方法,该方法底层调用内核态接口,此时发生了一次上下文切换(用户态->内核态),内核态将数据从磁盘拷贝到 kernel buffer,总共发生了一次上下文切换和一次数据拷贝。
- 内核态将数据从 kernel buffer 拷贝到 socket buffer,此时发生了一次数据拷贝,总共发生了一次数据拷贝。
- 内核态将数据从 socket buffer 拷贝到 网络接口,此时发生了一次数据拷贝,总共发生了一次数据拷贝。
- FileChannel.transferTo() 方法返回,此次 IO 结束,此时发生了一次上下文切换(内核态->用户态),总共发生了一次上下文切换和一次数据拷贝。
整个流程分为了四步,总共需要经过三次数据拷贝和两次上下文切换,,其中一次 CPU copy 和 两次 DMA copy。和传统 IO 相比减少了一次 CPU copy 和 两个上下文切换,和 mmap 相比减少了两次上下文切换。
mmap vs sendfile
如前所述,mmap 和 sendfile 最大的不同就是用户态进程是否可以操作数据,mmap 通过虚拟内存映射技术,是开发人员在 IO 的过程中可以修改数据,比如在发送文件之前要修改文件中第一行的数据,就必须使用 mmap,如果你的需求是直接将文件发送到网络接口中,那么推荐使用 sendfile,因为在该场景中它比 mmap 更快。
Linux 除了这两种 IO 技术还有其他的 IO 技术,如 splice,但是 Java 只支持这两种 IO 优化技术,而且这两种也是最常见的,所以这里只介绍了这两种 IO 优化技术。其实 mmap 和 sendfile 都不算真正的零拷贝,因为零拷贝的概念是整个 IO 过程中零次的 CPU 拷贝,mmap 和 sendfile 都需要一次 CPU 拷贝。
Java 中的抽象
在 Java 中,mmap 技术和 sendfile 技术的实现,都抽象在了 FileChannel 类中。下面介绍通过 FileChannel 来使用这两种 IO 技术的方式。
mmap
FileChannel 通过返回 MappedByteBuffer 来操作磁盘文件。
// 打开文件并创建 FileChannel
RandomAccessFile file = new RandomAccessFile("yourfile.txt", "rw");
FileChannel channel = file.getChannel();
// 创建 MappedByteBuffer
MappedByteBuffer mappedBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
// 读取数据
byte data = mappedBuffer.get();
// 写入数据
mappedBuffer.put((byte) 42);
// 关闭通道
channel.close();
file.close();
你可以像操作普通的字节数组一样,通过 MappedByteBuffer
来操作文件的数据,读取和写入操作都会直接影响到文件。mmap 技术的优势在于你将文件内容直接映射到内存中,避免了复制数据的开销,从而提高了文件 IO 操作的性能。这对于大型文件和需要频繁读写的文件非常有用。
需要注意的是,MappedByteBuffer 的大小不能超过文件的大小,并且文件的更改会立即反映到映射中,这可能会影响到其他访问同一个文件的程序。在多线程或多进程环境中使用 mapp 时要格外小心,确保同步访问。
使用 MappedByteBuffer 将文件发送到网络接口
// 创建 SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("remote-host", port));
// 创建 MappedByteBuffer
RandomAccessFile file = new RandomAccessFile("yourfile.txt", "r");
FileChannel channel = file.getChannel();
MappedByteBuffer mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
// 将数据写入 SocketChannel
socketChannel.write(mappedBuffer);
// 关闭资源
socketChannel.close();
channel.close();
file.close();
由上可知,MappedByteBuffer 可以用来简单的修改磁盘文件内容,这在大文件场景下非常拥有。
sendfile
FileChannel#transferTo 和 FileChannel#transferFrom 方法就是 Java 对 sendfile 的抽象。
使用 FileChannel.transferTo
方法发送文件到客户端:
FileChannel fileChannel = new FileInputStream("example.txt").getChannel();
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 12345));// 将文件内容发送到客户端
long transferred = 0;
long size = fileChannel.size();
while (transferred < size) {transferred += fileChannel.transferTo(transferred, size - transferred, socketChannel);
}fileChannel.close();
socketChannel.close();
在上面的示例中,transferTo
方法将文件内容直接从文件通道发送到套接字通道,避免了数据的复制。
使用 FileChannel.transferFrom
方法接收客户端发送的文件:
SocketChannel socketChannel = ServerSocketChannel.open().accept();
FileChannel fileChannel = new FileOutputStream("received.txt").getChannel();// 接收客户端发送的文件并保存到本地
long transferred = 0;
long size = Long.MAX_VALUE; // 你需要知道文件的大小
while (transferred < size) {transferred += fileChannel.transferFrom(socketChannel, transferred, size - transferred);
}fileChannel.close();
socketChannel.close();
在这个示例中,transferFrom
方法将文件内容直接从套接字通道接收到文件通道,也避免了数据的复制。
后话
如果你在之前看了很多其他讲解 Java 实现零拷贝的博文,可能有很多博文会提到 Channel 对应操作系统内核态缓存
,这句话是有问题的,我们看看 ChatGPT 怎么说:
参考:
https://springboot.io/t/topic/4843
https://zhuanlan.zhihu.com/p/78869158
https://blog.csdn.net/cringkong/article/details/80274148
https://www.jianshu.com/p/497e7640b57c
https://chat.openai.com/
相关文章:

Java 与零拷贝
零拷贝是由操作系统实现的,使用 Java 中的零拷贝抽象类库在支持零拷贝的操作系统上运行才会实现零拷贝,如果在不支持零拷贝的操作系统上运行,并不会提供零拷贝的功能。 简述内核态和用户态 Linux 的体系结构分为内核态(内核空间…...
AI性能指标解析:误触率与错误率
简介:随着人工智能(AI)技术的不断发展,它越来越多地渗透到我们日常生活的各个方面。从个人助手到自动驾驶,从语音识别到图像识别,AI正不断地改变我们与世界的互动方式。但你有没有想过,如何准确…...

count(*) 和 count(1) 有什么区别?哪个性能最好?
哪种 count 性能最好? count() 是什么? count() 是一个聚合函数,函数的参数不仅可以是字段名,也可以是其他任意表达式,该函数的作用是统计符合查询条件的记录中,函数指定的参数不为 NULL 的记录由多少条。…...
橡胶密封件为什么会老化?
橡胶密封件以其优良的密封性能被广泛应用于各个行业。然而,随着时间的推移,这些橡胶密封件往往会恶化和老化。在这篇文章中,我们将探讨橡胶密封件老化的原因。 1,导致橡胶密封件老化的主要因素之一是暴露在阳光和紫外线(UV)辐射下…...

Uboot中bootargs以及bootcmd设置
Uboot命令 一、Uboot基础命令 查看帮助信息: uboot#help打印环境变量: uboot#printenv其他命令: uboot#help ? - 帮助命令,等同于 help base - 打印或设置地址偏移量 bdinfo - 打印板级信息结构 boot …...

冠达管理:减肥药概念再度爆发,常山药业两连板,翰宇药业等大涨
减肥药概念12日盘中再度拉升,到发稿,常山药业“20cm”涨停,翰宇药业涨超14%,德展健康涨停,金凯生科涨近9%,争气股份、普利制药、昊帆生物涨约5%,诺泰生物、圣诺生物、华森制药等涨超4%。 常山药…...

实现在外网SSH远程访问内网树莓派的详细教程
文章目录 如何在局域网外SSH远程访问连接到家里的树莓派?如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpol…...

Pytorch框架详解
文章目录 引言1. 安装与配置1.1 如何安装PyTorch1.2 验证安装 2. 基础概念2.1 张量(Tensors)2.1.1 张量的基本特性2.1.2 创建张量2.1.3 张量操作 2.2 自动微分(Autograd)2.2.1 基本使用2.2.2 计算梯度2.2.3 停止追踪历史2.2.4 自定…...

2023年9月制造业NPDP产品经理国际认证报名来这错不了
产品经理国际资格认证NPDP是新产品开发方面的认证,集理论、方法与实践为一体的全方位的知识体系,为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会(PDMA)成立于1979年,是…...

linux(centos7)配置SSH免密登录
给三台机器配置主机名映射 在Windows系统中修改hosts文件,新增以下内容; 192.168.xxx.xxx bigdata_node1 192.168.xxx.xxx bigdata_node2 192.168.xxx.xxx bigdata_node33台Linux的/etc/hosts文件中,填入如下内容。 192.168.xxx.xxx bigda…...

cf 交互题
今天cf遇到了交互题,这个交互题的算法很很很简单,但是在交互上卡了,导致交上的代码都不算罚时。(更伤心了。 所以,现在写一下交互题的做法,印象深刻嘛。 交互题,就是跟机器进行交互。你代码运…...

成都瀚网科技有限公司:抖音怎么绑定抖音小店才好?
抖音是一款非常流行的短视频应用,为用户提供了一个展示才华、分享生活的平台。在抖音上,用户可以通过绑定抖音商店来销售自己的产品或服务,从而实现商业变现。那么,抖音如何绑定抖音商店呢? 1、抖音如何绑定抖音商店&a…...

大数据组件-Flink环境搭建
🥇🥇【大数据学习记录篇】-持续更新中~🥇🥇 个人主页:beixi 本文章收录于专栏(点击传送):【大数据学习】 💓💓持续更新中,感谢各位前辈朋友们支持…...

Java——》synchronized互斥性
推荐链接: 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…...

第十章 数组和指针
本章介绍以下内容: 关键字:static 运算符:&、*(一元) 如何创建并初始化数组 指针(在已学过的基础上)、指针和数组的关系 编写处理数组的函数 二维数组 人们通常借助计算机完成统计每月的支出…...

JVM系列 运行时数据区
系列文章目录 第一章 运行区实验 文章目录 系列文章目录前言一、堆(Heap)1.1、新生代/Young区1.1.1、Eden区1.1.2、Survival区 1.2、年老代(old区) 二、虚拟机栈(Stack)2.1、栈顶缓存技术2.2、溢出2.3、栈…...
软件测试/测试开发丨突破传统,革新测试:ChatGpt指引下的测试方案编写
点此获取更多相关资料 简介 测试方案是指描述需要被测产品的特性、测试的方法、测试环境的规划、测试工具的设计和选择、测试用例的设计方法、测试代码的设计方案。 我们常常需要根据产品的特性、测试策略等几个方向输出对应的测试方案。在写测试方案的过程中,常…...

JVM-垃圾回收器详解、参数配置
相关概念 并行和并发 并行(Parallel) 指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 并发(Concurrent) 指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行…...

计算机算法分析与设计(1)---求算法时间复杂性(手写例题)
文章目录 一、主定理求解二、递归树求解三、递归树求解含O的递归方程 一、主定理求解 二、递归树求解 三、递归树求解含O的递归方程...
MyBatisPlus 分页查询
首先要定义一个配置类 MybatisConfig 放在 config 类下 他的生效是通过拦截生效的 所以是要写拦截器的 (这段拦截器的配置是固定的 CV 也可以) Configuration public class MybatisConfig{Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){// 1.定义MybatisPlu…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...