Io 相关
一、Java IO 模型
(1)IO相关名称
- IO模型主要分类:
- 同步(synchronous) IO和异步(asynchronous) IO
- 阻塞(blocking) IO和非阻塞(non-blocking)IO
- 同步阻塞(blocking-IO)简称BIO
- 同步非阻塞(non-blocking-IO)简称NIO
- 异步非阻塞(Asynchronous-non-blocking-IO)简称AIO
- 同步和异步的区别:
- 同步: 发送一个请求,等待返回,再发送下一个请求,同步可以避免出现死锁,脏读的发生
- 异步:发送一个请求,不等待返回,随时可以再发送下一个请求,可以提高效率,保证并发。
- 主要区别:
在发起IO请求和实际IO操作时,实际IO操作是否阻塞。如果实际IO 操作阻塞请求进程(__请求进程需要等待或者轮询查看IO操作状态 __)就是同步IO。如果实际IO操作不阻塞请求进程,由OS进行操作 后将结果返回,这是异步IO
- 阻塞和非阻塞的区别:
- 阻塞:当视图对文件描述符或者网络套接字进行读写时,如果当时没有东西可读,或者暂时不可写,程序就进入等待状态,直到有东西读或者写。即阻塞即等待
- 非阻塞:当视图对文件描述符或者网络套接字进行读写时,如果没有东西可读,或者不可写,读写函数马上返回,无须等待。
- 主要区别:
IO 操作主要分为两个步骤,即发起 IO 请求和实际 IO 操作,阻 塞与非阻塞的区别就在于第一个步骤是否阻塞。 若发起 IO 请求后请求线程一直等待实际 IO 操作完成,则为阻 塞 IO;若发起 IO 请求后请求线程返回而不会一直等待,即为非 阻塞 IO。
(2)BIO
- 概念说明:
- 定义:数据的读取必须阻塞在一个线程内等待其完成
- TCSC(实例代码):BIO 的code
- 场景:
- 例子:这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做
- 使用范围:BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择。
(3)NIO
- 概念说明:
- non-blocking的简称
- TCSC(实例代码):NIO的code
- 场景:
- 例子:还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作
- 使用范围:NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂。
(4)AIO
- 概念说明:
- 异步IO的简称
- TCSC(实例代码):AIO的code
- 场景:
- 例子:异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
- 使用范围:AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
二、Linux IO模型
(1)相关知识
- 用户空间和内核空间:
- 现代的操作系统都是采用虚拟存储器。
- 系统的寻址空间取决于系统的位数,32位的寻址空间为4G(2^32)。
- 操作系统的核心是内核,它独立于普通的应用程序,可以访问受保护的内存地址,并且拥有所有的硬件的访问权限
- 针对操作系统,一部分的供内核使用的内存叫做内核空间
- 供用户程序使用的内存叫做用户空间
- 缓存IO:
- 缓存IO:被称为标准IO,基本所有的操作系统都是缓存IO。在Linux中的缓存IO中,操作系统会将Io的数据缓存到文件系统的页缓存中(Page Cache)
- 一般的IO操作会先将数据Copy到内核空间的缓存区,然后在copy到用户空间
- 数据传输中需要在内核空间和用户空间来回copy,CPU和内存的开销比较大,由此衍生出zero-copy技术
- 一般IO操作的流程:
- 第一阶段:等待数据准备
- 第二阶段:将数据从内核空间copy到进程中
(2)IO模型
-
同步阻塞IO:
- 在同步阻塞IO的情况下,用户空间执行一个系统调用(从内核空间),此时,该进程会block住,直到数据准备好,然后从内核空间Copy到用户空间,再进行数据处理逻辑
- 流程图:
- 代码:code
-
同步非阻塞IO:
- 在同步非阻塞IO的情况下,用户执行一个系统调用,进程不会被block住,内核空间会立刻返回一个flag,然后进程可以先执行其他的任务,再发起系统调用,重复上述过程。一般这过程被称为轮询。此处的内核空间到用户空间的数据copy依旧是block的。
- 流程图:
)
- 代码:code
-
IO 多路复用:
- 事件驱动IO
- 流程图:
- 代码:code
-
信号驱动式IO:
- 首先我们允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据
- 流程图:
- 代码: code
-
异步非阻塞 IO:
-
IO 模型的比较:
(3)jvm 中对linux io的封装(需要openjdk源码)
- jvm 中对io操作的封装
- io_util_md.h ,io_util_md.c ,io_util.c ,libnio
- jvm中的native方法包:share/native
Linux 内存相关
内存
- 基本概念:
- 地址空间:操作系统对物理内存的抽象。可以来访问内存的地址集合,每个进程都有自己的地址空间,并且在每个进程看来都是等于物理内存大小的空间,进程可以任意的去访问,不需要担心影响其他的进程,因为访问的都是虚拟地址。
- 地址空间的划分:4GB的内存中,有1GB的是共享空间,也叫做内核空间,其他的都是进程独享的又叫做用户空间。当4GB的空间都满了,就会把一部分的无用的内存数据保存到磁盘中来腾出空间,这种技术叫做虚拟内存。
- 内核空间/用户空间:内核空间的数据在用户模式下的进程是不可见的,当进程切换到内核模式的时候才可以对内核空间的数据访问。
- 页:
- 32位系统中,每个进程都有4G的地址空间,但是程序在运行的时候并不需要程序中所有的内容,需要的只是当前正在执行的一部分和相关的数据就可以,因此可以将整个地址空间分成一个个连续的大小固定的块(比如4KB),只要那些需要的块在内存中即可,这一个个的块就是页(page)。这样的好处就是当程序运行或者停止时,系统不用把整个进程的内容移进移出,而只要移动一个个页就可以,这样既提高了效率又节省了空间,才使得多个进程能够同时存在于内存中。注意,这里的页是指虚拟地址空间里连续的一段,而物理内存中这样连续的段则称为page frame,它们的大小相同,进程的页放进内存中时就放到一个个的page frame中。
- 逻辑地址到物理地址的转换:
Linux为每个进程维护了一个page table,这个表里的每一条记录表示一个page.每条记录中通过一些位标识了这个页是否存在于实际内存中,允许什么方式的访问,是否被修改过,是否正在被使用,是否进行缓存等信息,其中最重要的就是page frame number,系统就是由此得到的该页在实际内存中的位置。
- 页的回收:在系统运行期间,随着越来越多的程序的启动和运行,越来越多的物理内存会被占用,而系统必须保证有空闲的内存来维持正常的运行,Linux使用了一种叫PFRA(page frame reclaiming algorithm) 的算法将一些暂时没用的page frame释放来腾出空间,的目标就是从内存中选出要释放的page frame。
- 地址转换:
-
基于硬件的动态地址转换:
cpu中添加俩个硬件寄存器(MMU),基址寄存器(保存进程物理地址的起始地址),界限寄存器(进程的地址边界)。通过这俩寄存器可以做到地址转换,具体的实现如下公式:
物理地址 = 虚拟地址 + 基本地址 -
快速地址转换(TLB): 参考:TLB的作用和工作原理
-