当前位置: 首页 > news >正文

Java 与零拷贝

零拷贝是由操作系统实现的,使用 Java 中的零拷贝抽象类库在支持零拷贝的操作系统上运行才会实现零拷贝,如果在不支持零拷贝的操作系统上运行,并不会提供零拷贝的功能。

简述内核态和用户态

Linux 的体系结构分为内核态(内核空间)和用户态(用户空间),我们知道一台计算器拥有 CPU、网卡、内存和磁盘等硬件资源,内核态相当于 Linux Core,它是一种特殊的软件程序,也可以看成操作系统本身,它控制着计算机的所有硬件资源,并给用户态的进程分配所需的资源,用户态的进程不能直接访问硬件资源,它需要通过内核态体提供的接口来间接操作硬件资源。两者的关系图如下:
在这里插入图片描述
JVM 就是一个用户态进程。

Linux 的 IO 发展史

传统 IO

在 Java 程序中,你要从磁盘读取文件,然后将文件发送到网络中,在这个场景下,看看传统 IO 数据在操作系统中的流转情况如下:
请添加图片描述

  1. 开发人员调用 InputStream.read() 方法,InputStream.read() 底层调用了操作系统的 read() 接口来从磁盘读取数据,此时发生了一次上下文切换(用户态->内核态),操作系统将数据从磁盘拷贝到 read buffer,此时发生了一次数据拷贝。总共发生了一次上下文切换和一次数据拷贝。
  2. 操作系统将数据从 read buffer 拷贝到应用进程(JVM),即操作系统 read() 接口的返回,此时发生了一次数据拷贝和一次上下文切换(内核态->用户态),总共发生了一次上下文切换和一次数据拷贝。
  3. 开发人员调用 SocketOutputStream.write() 方法将数据发送到网络中,数据被从应用进程(JVM)拷贝到内核态的 Socket buffer 中,此时发生了一次数据拷贝一次上下文切换(用户态->内核态)此时在用户态,总共发生了一次上下文切换和一次数据拷贝。
  4. 内核态(操作系统)调用底层接口将数据从 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 中将内核态的虚拟内存和用户态的虚拟内存映射到了同一块物理内存中,这样数据在被拷贝到内核态之后就不需要再拷贝到用户态了,用户态通过虚拟内存来和内核态操作同一块内存,整个流程如下:

  1. 开发人员调用 InputStream.read() 方法,InputStream.read() 底层调用了操作系统的 read() 接口来从磁盘读取数据,此时发生了一次上下文切换(用户态->内核态),操作系统将数据从磁盘拷贝到 read buffer,此时发生了一次数据拷贝,总共发生了一次上下文切换和一次数据拷贝。
  2. InputStream.read() 方法返回,此时发生了上下文切换(内核态->用户态),但是数据不需要再拷贝到用户态,用户态中的应用进程(JVM)通过虚拟内存技术和内核态共用一块物理内存,用户态对内存的操作会直接反映到内核态,总共发生了一次上下文切换。
  3. 开发人员调用 SocketOutputStream.write() 准备发送数据到网络中,此时发生了一次上下文切换(用户态->内核态),然后内核态将与用户态共享的那块内存拷贝到内核态的 socket buffer 中,总共发生了一次上下文切换和一次数据拷贝。
  4. 内核态将数据从 socket buffer 中拷贝到网络接口中,然后 SocketOutputStream.write() 方法返回写入结果,此时发生了一次上下文切换(内核态->用户态),总共发生了一次上下文切换和一次数据拷贝。

整个流程分为了四步,总共需要经过三次数据拷贝和四次上下文切换,,其中一次 CPU copy 和 两次 DMA copy。和传统 IO 相比减少了一次 CPU copy,提高了 IO 的性能以及减少了 CPU 的负载。

sendfile

Linux 2.1 出现了 sendfile 技术,使用它之后整个 IO 流程如下:
在这里插入图片描述
sendfile 和 mmap 有点类似,相比 mmap,它取消了内存映射的部分,这也导致了用户态的进程无法操作要发送的数据(磁盘文件),但是在不需要操作数据的场景中比 mmap 的性能更好。整个流程如下:

  1. 开发人员调用 FileChannel.transferTo() 方法,该方法底层调用内核态接口,此时发生了一次上下文切换(用户态->内核态),内核态将数据从磁盘拷贝到 kernel buffer,总共发生了一次上下文切换和一次数据拷贝。
  2. 内核态将数据从 kernel buffer 拷贝到 socket buffer,此时发生了一次数据拷贝,总共发生了一次数据拷贝。
  3. 内核态将数据从 socket buffer 拷贝到 网络接口,此时发生了一次数据拷贝,总共发生了一次数据拷贝。
  4. 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 与零拷贝

零拷贝是由操作系统实现的&#xff0c;使用 Java 中的零拷贝抽象类库在支持零拷贝的操作系统上运行才会实现零拷贝&#xff0c;如果在不支持零拷贝的操作系统上运行&#xff0c;并不会提供零拷贝的功能。 简述内核态和用户态 Linux 的体系结构分为内核态&#xff08;内核空间…...

AI性能指标解析:误触率与错误率

简介&#xff1a;随着人工智能&#xff08;AI&#xff09;技术的不断发展&#xff0c;它越来越多地渗透到我们日常生活的各个方面。从个人助手到自动驾驶&#xff0c;从语音识别到图像识别&#xff0c;AI正不断地改变我们与世界的互动方式。但你有没有想过&#xff0c;如何准确…...

count(*) 和 count(1) 有什么区别?哪个性能最好?

哪种 count 性能最好&#xff1f; count() 是什么&#xff1f; count() 是一个聚合函数&#xff0c;函数的参数不仅可以是字段名&#xff0c;也可以是其他任意表达式&#xff0c;该函数的作用是统计符合查询条件的记录中&#xff0c;函数指定的参数不为 NULL 的记录由多少条。…...

橡胶密封件为什么会老化?

橡胶密封件以其优良的密封性能被广泛应用于各个行业。然而&#xff0c;随着时间的推移&#xff0c;这些橡胶密封件往往会恶化和老化。在这篇文章中&#xff0c;我们将探讨橡胶密封件老化的原因。 1&#xff0c;导致橡胶密封件老化的主要因素之一是暴露在阳光和紫外线(UV)辐射下…...

Uboot中bootargs以及bootcmd设置

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

冠达管理:减肥药概念再度爆发,常山药业两连板,翰宇药业等大涨

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

实现在外网SSH远程访问内网树莓派的详细教程

文章目录 如何在局域网外SSH远程访问连接到家里的树莓派&#xff1f;如何通过 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 张量&#xff08;Tensors&#xff09;2.1.1 张量的基本特性2.1.2 创建张量2.1.3 张量操作 2.2 自动微分&#xff08;Autograd&#xff09;2.2.1 基本使用2.2.2 计算梯度2.2.3 停止追踪历史2.2.4 自定…...

2023年9月制造业NPDP产品经理国际认证报名来这错不了

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…...

linux(centos7)配置SSH免密登录

给三台机器配置主机名映射 在Windows系统中修改hosts文件&#xff0c;新增以下内容&#xff1b; 192.168.xxx.xxx bigdata_node1 192.168.xxx.xxx bigdata_node2 192.168.xxx.xxx bigdata_node33台Linux的/etc/hosts文件中&#xff0c;填入如下内容。 192.168.xxx.xxx bigda…...

cf 交互题

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

成都瀚网科技有限公司:抖音怎么绑定抖音小店才好?

抖音是一款非常流行的短视频应用&#xff0c;为用户提供了一个展示才华、分享生活的平台。在抖音上&#xff0c;用户可以通过绑定抖音商店来销售自己的产品或服务&#xff0c;从而实现商业变现。那么&#xff0c;抖音如何绑定抖音商店呢&#xff1f; 1、抖音如何绑定抖音商店&a…...

大数据组件-Flink环境搭建

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…...

Java——》synchronized互斥性

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…...

第十章 数组和指针

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

JVM系列 运行时数据区

系列文章目录 第一章 运行区实验 文章目录 系列文章目录前言一、堆&#xff08;Heap&#xff09;1.1、新生代/Young区1.1.1、Eden区1.1.2、Survival区 1.2、年老代&#xff08;old区&#xff09; 二、虚拟机栈&#xff08;Stack&#xff09;2.1、栈顶缓存技术2.2、溢出2.3、栈…...

软件测试/测试开发丨突破传统,革新测试:ChatGpt指引下的测试方案编写

点此获取更多相关资料 简介 测试方案是指描述需要被测产品的特性、测试的方法、测试环境的规划、测试工具的设计和选择、测试用例的设计方法、测试代码的设计方案。 我们常常需要根据产品的特性、测试策略等几个方向输出对应的测试方案。在写测试方案的过程中&#xff0c;常…...

JVM-垃圾回收器详解、参数配置

相关概念 并行和并发 并行&#xff08;Parallel&#xff09; 指多条垃圾收集线程并行工作&#xff0c;但此时用户线程仍然处于等待状态。 并发&#xff08;Concurrent&#xff09; 指用户线程与垃圾收集线程同时执行&#xff08;但不一定是并行的&#xff0c;可能会交替执行…...

计算机算法分析与设计(1)---求算法时间复杂性(手写例题)

文章目录 一、主定理求解二、递归树求解三、递归树求解含O的递归方程 一、主定理求解 二、递归树求解 三、递归树求解含O的递归方程...

MyBatisPlus 分页查询

首先要定义一个配置类 MybatisConfig 放在 config 类下 他的生效是通过拦截生效的 所以是要写拦截器的 (这段拦截器的配置是固定的 CV 也可以) Configuration public class MybatisConfig{Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){// 1.定义MybatisPlu…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

c#开发AI模型对话

AI模型 前面已经介绍了一般AI模型本地部署&#xff0c;直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型&#xff0c;但是目前国内可能使用不多&#xff0c;至少实践例子很少看见。开发训练模型就不介绍了&am…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

在 Spring Boot 项目里,MYSQL中json类型字段使用

前言&#xff1a; 因为程序特殊需求导致&#xff0c;需要mysql数据库存储json类型数据&#xff0c;因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...