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…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

云原生玩法三问:构建自定义开发环境
云原生玩法三问:构建自定义开发环境 引言 临时运维一个古董项目,无文档,无环境,无交接人,俗称三无。 运行设备的环境老,本地环境版本高,ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...