程序运行在内存以及IO的体现

  首先普及一下常识,如图所示:

  1、在整个内存空间中,跑着各种各样的程序,有Java程序、C程序,他们共用一块内存空间。

  2、对于Java程序,JVM会申请一块堆空间,通过Xmx可以设置,其余空间是堆外空间,其中每个线程有自己的线程栈,保证线程内存隔离,堆空间使用完以后,会触发Full FC,堆外空间所有进程可共享使用,无限制。

  3、所有系统运行的程序都必须通过操作系统内核进行IO操作,操作系统也是程序,也需要一定的内存空间。

一、使用Buffer代替基本IO

  我们写一个方法,此方法使用了FileWriter进行了文件的写操作,我们都知道不调用flush()可能会造成数据丢失,那么为什么呢,flush操作到底做了些什么呢?

public void fileIO() throws Exception {File file = new File("/Volumes/work/temp/temp.txt");if (file.exists()) {file.delete();}file.createNewFile();FileInputStream fileInputStream = new FileInputStream(file);FileWriter fileWriter = new FileWriter(file);fileWriter.write("hello");fileWriter.write("world");fileWriter.write("\nhello world");Thread.sleep(99999);fileWriter.flush();fileWriter.close();
}

  我们知道我们在写数据的时候不管是C还是Java都会有两个缓冲区,一个是操作系统的缓冲区sys buffer,还有一个是程序的缓冲区program buffer。那么刚刚的flush操作是把程序的缓冲区内容写到了系统缓冲区,还是把系统缓冲区的内容刷到了硬盘呢?因此我们在调用flush()之前进行了sleep操作,检查在flush之前,具体的内容并未写到temp.txt文件中,当我们睡眠时间结束后,可以看到调用flush方法后则把内容写到了文件中,如图:

  实际上FileWriter基本IO是没有先写程序缓存的,那么实际上FileWriter的每次write操作都发生了系统调用,直接写到了内核的系统缓冲区,然后当调用flush操作时,系统缓冲区的内容再刷到了硬盘上。

关于C/C++Linux后台服务器开发高级架构师学习视频 点击 linux服务器学习资料 获取,内容知识点包括Linux,Nginx,ZeroMQ,MySQL,Redis,线程池,MongoDB,ZK,Linux内核,CDN,P2P,epoll,Docker,TCP/IP,协程,DPDK等等。免费学习地址:C/C++Linux服务器开发高级架构师/Linux后台架构师

  因此IO性能提升第一步:无论是InputStream还是FileWriter,都是底层的IO,是直接调用内核的,因此写入都是直接写入到内核的系统buffer,因此在使用IO的时候不要使用这类底层IO,否则发生大量系统调用,降低系统性能,而是应该先写到程序buffer然后再调用系统IO,当程序buffer满了后才通过系统调用写到系统buffer空间中,这样减少了大量系统调用,提升了性能。

  那么什么时候系统buffer中的数据才写入到硬盘呢?2种情况:①.系统buffer满了;②.执行了flush()操作,也就是发生了fsync的系统调用。

public void bufferedIO() throws Exception {BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file), 1024);BufferedReader reader = new BufferedReader(new FileReader(file));bufferedOutputStream.write("hello world\nhello world".getBytes());bufferedOutputStream.flush();bufferedOutputStream.close();String line = reader.readLine();System.out.println(line);
}

  还有另一种是直接写入到内存的,如代码:

public void memoryIO() throws Exception {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);// 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。可以通过toString()和toByteArray()获取数据byteArrayOutputStream.write("hello world".getBytes());String string = byteArrayOutputStream.toString();System.out.println(string);byte[] inData = byteArrayOutputStream.toByteArray();ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inData);byte[] data = new byte[1024];byteArrayInputStream.read(data);System.out.println(new String(data));byteArrayOutputStream.flush();byteArrayOutputStream.close();
}

  这样就类似于Redis一样,是对内存进行直接操作,因此这样也能提高不少效率。

二、堆外内存mmap直接映射内核空间

  如下图:

  1、如果数据在堆内,那么在写入磁盘时,会先序列化后拷贝到堆外,然后堆外再write到系统内核缓冲区,内核缓冲区通过系统调用fsync写入到磁盘;

  2、如果数据是在堆外内存,那么也需要先拷贝到内核缓冲区,在fsync系统调用后也才写入到磁盘;

  3、通过系统调用mmap申请一块虚拟的地址空间,这片空间用户程序和系统内核都可以访问到。

  如下代码:

public void randomIO() throws Exception{RandomAccessFile randomAccessFile = new RandomAccessFile(file,"rw");randomAccessFile.write("hello world\nhello chicago\nhello ChengDu".getBytes());FileChannel channel = randomAccessFile.getChannel();/*** 堆外的数据如果想写磁盘,通过系统调用,经历数据从用户空间拷贝到内核空间* 堆外mapedBuffer的数据内核直接处理*/// 分配在了堆上  heap空间// ByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 分配在了堆外  offheap空间// ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);//mmap  内核系统调用  堆外空间,直接映射MappedByteBuffer byteBuffer = channel.map(FileChannel.MapMode.READ_WRITE,0,2018);byteBuffer.put("byteBuffer testing".getBytes());randomAccessFile.seek(12);randomAccessFile.write("*****".getBytes());
}

  可以看到通过FileChannel的map方法实现系统调用,申请mmap直接映射空间,数据无需由用户空间拷贝到系统空间,节省了一次拷贝的时间损耗,提升了性能。

三、sendfile零拷贝

  在Linux系统中。存储在文件中的信息通过网络传送给客户这样的简单过程中,所涉及的操作。下面是其中的部分简单代码:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

  其实过程中实现了多次拷贝,性能很低,如图可知:

  步骤一:系统调用read导致了从用户空间到内核空间的上下文切换。DMA模块从磁盘中读取文件内容,并将其存储在内核空间的缓冲区内,完成了第1次复制。

  步骤二:数据从内核空间缓冲区复制到用户空间缓冲区,完成了第2次复制,之后系统调用read返回,这导致了从内核空间向用户空间的上下文切换。此时,需要的数据已存放在指定的用户空间缓冲区内(参数tmp_buf),程序可以继续下面的操作。

  步骤三:系统调用write导致从用户空间到内核空间的上下文切换。数据从用户空间缓冲区被再次复制到内核空间缓冲区,完成了第3次复制。不过,这次数据存放在内核空间中与使用的socket相关的特定缓冲区中,而不是步骤一中的缓冲区。

  步骤四:系统调用返回,导致了第4次上下文切换。第4次复制在DMA模块将数据从内核空间缓冲区传递至协议引擎的时候发生,这与我们的代码的执行是独立且异步发生的。你可能会疑惑:“为何要说是独立、异步?难道不是在write系统调用返回前数据已经被传送了?write系统调用的返回,并不意味着传输成功——它甚至无法保证传输的开始。调用的返回,只是表明以太网驱动程序在其传输队列中有空位,并已经接受我们的数据用于传输。可能有众多的数据排在我们的数据之前。除非驱动程序或硬件采用优先级队列的方法,各组数据是依照FIFO的次序被传输的(图1中叉状的DMA copy表明这最后一次复制可以被延后)。

  因此就诞生了零拷贝:

sendfile(socket, file, len);

  如图:

  步骤一:sendfile系统调用导致文件内容通过DMA模块被复制到内核缓冲区中。

  步骤二:记录数据位置和长度的描述符被加入到socket缓冲区中,DMA模块将数据直接从内核缓冲区传递给协议引擎。

  基于以上实现,最终实现了“零拷贝”。

高性能IO应用

  在现实应用中,Kafka常用来进行日志处理,存在着大量的IO,其高性能就是建立在IO上的优化,如图:

如何实现高性能的IO及IO原理?相关推荐

  1. 详解 epoll 原理【Redis,Netty,Nginx实现高性能IO的核心原理】

    epoll原理剖析:epoll原理剖析以及reactor模型应用 redis源码分析:redis源码分析及driver现实 Nginx模块开发:Nginx源码从模块开发入手,3个项目弄透nginx模块 ...

  2. 图文详解 epoll 原理【Redis,Netty,Nginx实现高性能IO的核心原理】epoll 详解

    [Redis,Netty,Nginx 等实现高性能IO的核心原理] I/O 输入输出(input/output)的对象可以是文件(file), 网络(socket),进程之间的管道(pipe).在li ...

  3. io多路复用的原理和实现_IO多路复用机制详解

    select,poll,epoll机制区别总结: 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞I ...

  4. 高并发IO的底层原理

    从基础讲起,IO的原理和模型是隐藏在编程知识底下的,是开发人员必须掌握的基础原理,是基础的基础,更是通关大公司面试的必备知识. 本章从操作系统的底层原理入手.通过图文并茂的方式,为大家深入剖析高并发I ...

  5. io多路复用的原理和实现_多路复用IO内幕

    什么是多路复用IO 多路复用IO (IO multiplexing) 是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程.在Linux系统中,常用的 多路复用IO 手段有 sele ...

  6. IO多路复用底层原理及源码解析

    基本概念 1. 关于linux文件描述符 在Linux中,一切都是文件,除了文本文件.源文件.二进制文件等,一个硬件设备也可以被映射为一个虚拟的文件,称为设备文件.例如,stdin 称为标准输入文件, ...

  7. 《Netty、Redis、Zookeeper高并发实战》2️⃣高并发IO的底层原理

    文章目录 1.IO读写的基础原理 1.1 内核缓冲区与进程缓存区 1.2 详解典型的系统调用流程 2.四种主要的IO模型 2.1 同步阻塞IO(Blocking IO) 2.2 同步非阻塞IO(Non ...

  8. 图解四大IO模型与原理

    IO模型 1. IO读写原理 无论是Socket的读写还是文件的读写,在Java层面的应用开发或者是Linux系统底层开发,都属于input和输出output的处理,简称为IO读写.在原理和处理流程上 ...

  9. IO 端口和IO 内存(原理篇)

    CPU要想控制所链接的设备,不可避免需要通过IO(input/output)与外设打交道,CPU通过IO操纵设备上的寄存器等来实现对 设备的控制.一般厂商按照IO空间性质将IO划分为IO 端口和IO内 ...

最新文章

  1. fastadmin的基本用法 自动生成crud模块
  2. Python 技术篇-百度语音合成API接口调用演示
  3. 圆形的CNN卷积核?华中科大清华黄高团队康奈尔提出圆形卷积,进一步提升卷积结构性能!
  4. python-time.time()
  5. boost::fibers::barrier用法的测试程序
  6. docker 私有仓库与Harbor
  7. Web服务器故障的奇怪原因
  8. 为什么程序员要尽量少写代码
  9. Python爬虫中最重要、最常见、一定要熟练掌握的库
  10. visual studio code typescript 防止 import statement 断行
  11. 表格数据的识别与提取
  12. 从1.5K到18K 一个程序员的5年成长之路
  13. 字体直链提取器_MIUI主题直链提取器下载-MIUI主题直链提取软件 v1.3.5_5577安卓网...
  14. iOS 10 消息推送(UserNotifications)秘籍总结(一
  15. 我们上语文英语音乐计算机和美术英语,“制作课程表”教学设计.ppt
  16. No module named ‘torchvision.models.feature_extraction‘
  17. 计算机病毒是一种能破坏计算机运行的,计算机病毒是一种能破坏计算机运行的()。...
  18. java如何编写一个qq宠物程序_肿么用java编写一个QQ宠物喂食的程序?
  19. 2022年第十四届全国大学生数学竞赛
  20. 求解非齐次线性方程组算法

热门文章

  1. 从源码编译安装软件经验+技巧
  2. PHP如何引用word模板生成word文件
  3. Unreal动画导入导出
  4. About signal in linux environment
  5. R关于一些字符串报错
  6. 20220316在MT6739的android10下增加VT设备tty1
  7. GNS3思科模拟器:三层交换机技术
  8. 牛客 21297 手机号码
  9. 关于应广单片机PMS150烧录的问题
  10. java 线程池技术_JAVA36计之---线程池技术