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…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
