@ -3841,440 +3841,6 @@ public void vectorTest(){
# I/O
Linux/Unix常见IO模型: **阻塞( Blocking I/O) **、**非阻塞( Non-Blocking I/O) **、**IO多路复用( I/O Multiplexing) **、 ** 信号驱动 I/O( Signal Driven I/O) **(不常用)和**异步( Asynchronous I/O) **。网络IO操作主要涉及到**内核**和**进程**,其主要分为两个过程:
- 内核等待数据可操作(可读或可写)——阻塞与非阻塞
- 内核与进程之间数据的拷贝——同步与异步
## 基础概念
**① 阻塞( Blocking) 和非阻塞( Non-blocking) **
阻塞和非阻塞发生在内核等待数据可操作(可读或可写)时,指做事时是否需要等待应答。
- ** 阻塞:** 内核检查数据不可操作,则不立即返回
- ** 非阻塞:** 内核检查数据不可操作,则立即返回
**② 同步( Synchronous) 和异步( Asynchronous) **
同步和异步发生在内核与进程交互时, 进程触发IO操作后是否需要等待或轮询查看结果。
- ** 同步:** 触发IO操作 → 等待或轮询查看结果
- ** 异步:** 触发IO操作 → 直接返回去做其它事, IO处理完后内核主动通知进程
### 阻塞I/O
当用户程序执行 `read` ,线程会被阻塞,一直等到内核数据准备好,并把数据从内核缓冲区拷贝到应用程序的缓冲区中,当拷贝过程完成,`read` 才会返回。阻塞等待的是 ** 内核数据准备好** 和 ** 数据从内核态拷贝到用户态** 两个过程。过程如下图:

### 非阻塞I/O
非阻塞的 `read` 请求在数据未准备好的情况下立即返回,可以继续往下执行,此时应用程序不断轮询内核,直到数据准备好,内核将数据拷贝到应用程序缓冲区,`read` 调用才可以获取到结果。过程如下图:

注意,**这里最后一次 read 调用,获取数据的过程,是一个同步的过程,是需要等待的过程。这里的同步指的是内核态的数据拷贝到用户程序的缓存区这个过程。**
### 同步I/O
无论 `read` 和 `send` 是 `阻塞I/O` ,还是 `非阻塞I/O` 都是同步调用。因为在 `read` 调用时,内核将数据从内核空间拷贝到用户空间的过程都是需要等待的,即这个过程是同步的,如果内核实现的拷贝效率不高,`read` 调用就会在这个同步过程中等待比较长的时间。
### 异步I/O
真正的异步 I/O 是`内核数据准备好` 和 `数据从内核态拷贝到用户态` 这两个过程都不用等待。
当我们发起 `aio_read` (异步 I/O) 之后,就立即返回,内核自动将数据从内核空间拷贝到用户空间,这个拷贝过程同样是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作。过程如下图:

## Reactor模式
`Reactor 模式` 即 I/O 多路复用监听事件, 收到事件后根据事件类型分配( Dispatch) 给某个进程/线程。其主要由 `Reactor` 和 `处理资源池` 两个核心部分组成:
- **Reactor** :负责监听和分发事件。事件类型包含连接事件、读写事件
- ** 处理资源池**: 负责处理事件。如: read -> 业务逻辑 -> send
Reactor 模式是灵活多变的,可以应对不同的业务场景,灵活在于:
- Reactor 的数量可以只有一个,也可以有多个
- 处理资源池可以是单个进程/线程,也可以是多个进程/线程
将上面的两个因素排列组设一下,理论上就可以有 4 种方案选择:
- ** 单 Reactor 单进程/线程**
- ** 单 Reactor 多进程/线程**
- ** 多 Reactor 单进程/线程**:相比 `单Reactor单进程/线程` 方案不仅复杂而且没有性能优势,因此可以忽略
- ** 多 Reactor 多进程/线程**
### 单Reactor单进程/单线程
一般来说, C 语言实现的是`单Reactor单进程`的方案,因为 C 语编写完的程序,运行后就是一个独立的进程,不需要在进程中再创建线程。而 Java 语言实现的是「单 Reactor 单线程」的方案,因为 Java 程序是跑在 Java 虚拟机这个进程上面的,虚拟机中有很多线程,我们写的 Java 程序只是其中的一个线程而已。以下是「`单 Reactor单进程`」的方案示意图:

可以看到进程里有 `Reactor` 、`Acceptor`、`Handler` 这三个对象:
- `Reactor` 对象的作用是监听和分发事件
- `Acceptor` 对象的作用是获取连接
- `Handler` 对象的作用是处理业务
对象里的 `select` 、`accept`、`read`、`send` 是系统调用函数,`dispatch` 和 `业务处理` 是需要完成的操作,其中 `dispatch` 是分发事件操作。
**工作流程**
- `Reactor` 对象通过 `select` ( IO多路复用接口) 监听事件,收到事件后通过 `dispatch` 进行分发,具体分发给 `Acceptor` 对象还是 `Handler` 对象,还要看收到的事件类型
- 如果是连接建立的事件,则交由 `Acceptor` 对象进行处理,`Acceptor` 对象会通过 `accept` 方法 获取连接,并创建一个 `Handler` 对象来处理后续的响应事件
- 如果不是连接建立事件, 则交由当前连接对应的 `Handler` 对象来进行响应
- `Handler` 对象通过 `read` -> 业务处理 -> `send` 的流程来完成完整的业务流程
**优缺点**
- ** 优点**
- 因为全部工作都在同一个进程内完成,所以实现起来比较简单
- 不需要考虑进程间通信,也不用担心多进程竞争
- ** 缺点**
- 因为只有一个进程,无法充分利用 多核 `CPU` 的性能
- `Handler` 对象在业务处理时,整个进程是无法处理其它连接事件,如果业务处理耗时比较长,那么就造成响应的延迟
**使用场景**
单Reactor单进程的方案`不适用计算机密集型的场景`, `只适用于业务处理非常快速的场景`。如: Redis 是由 C 语言实现的, 它采用的正是「单Reactor单进程」的方案, 因为 Redis 业务处理主要是在内存中完成,操作的速度是很快的,性能瓶颈不在 CPU 上,所以 Redis 对于命令的处理是单进程的方案。
### 单Reactor多线程/多进程
如果要克服`单 Reactor 单线程/单进程`方案的缺点,那么就需要引入多线程/多进程,这样就产生了**单Reactor多线程/多进程**的方案。具体方案的示意图如下:

**工作流程**
- `Reactor` 对象通过 `select` ( IO 多路复用接口) 监听事件,收到事件后通过 `dispatch` 进行分发,具体分发给 `Acceptor` 对象还是 `Handler` 对象,还要看收到的事件类型
- 如果是连接建立的事件,则交由 `Acceptor` 对象进行处理,`Acceptor` 对象会通过 `accept` 方法获取连接,并创建一个 `Handler` 对象来处理后续的响应事件
- 如果不是连接建立事件, 则交由当前连接对应的 `Handler` 对象来进行响应
- `Handler` 对象不再负责业务处理,只负责数据的接收和发送,`Handler` 对象通过 `read` 读取到数据后,会将数据发给子线程里的 `Processor` 对象进行业务处理
- 子线程里的 `Processor` 对象就进行业务处理,处理完后,将结果发给主线程中的 `Handler` 对象,接着由 `Handler` 通过 `send` 方法将响应结果发送给 `client`
**单Reator多线程**
- ** 优势**:能够充分利用多核 `CPU` 的能力
- ** 缺点**:带来了多线程竞争资源问题(如需加互斥锁解决)
**单Reactor多进程**
- ** 缺点**
- 需要考虑子进程和父进程的双向通信
- 进程间通信远比线程间通信复杂
另外,`单Reactor` 的模式还有个问题,因为一个 `Reactor` 对象承担所有事件的 `监听` 和 `响应` ,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能瓶颈。
### 多Reactor多进程/多线程
要解决 `单Reactor` 的问题,就是将 `单Reactor` 实现成 `多Reactor` ,这样就产生了 ** 多Reactor多进程/线程** 方案。其方案的示意图如下(以线程为例):

**工作流程**
- 主线程中的 `MainReactor` 对象通过 `select` 监控连接建立事件,收到事件后通过 `Acceptor` 对象中的 `accept` 获取连接,将新的连接分配给某个子线程
- 子线程中的 `SubReactor` 对象将 `MainReactor` 对象分配的连接加入 `select` 继续进行监听,并创建一个 `Handler` 用于处理连接的响应事件
- 如果有新的事件发生时,`SubReactor` 对象会调用当前连接对应的 `Handler` 对象来进行响应
- `Handler` 对象通过 `read` -> 业务处理 -> `send` 的流程来完成完整的业务流程
**方案优势**
`多Reactor多线程` 的方案虽然看起来复杂的,但是实际实现时比 `单Reactor多线程` 的方案要简单的多,原因如下:
- ** 分工明确**:主线程只负责接收新连接,子线程负责完成后续的业务处理
- ** 主线程和子线程的交互很简单**:主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端
**应用场景**
- `多Reactor多线程` :开源软件 `Netty` 、`Memcache`
- `多Reactor多进程` :开源软件 `Nginx` 。不过 Nginx 方案与标准的多Reactor多进程有些差异, 具体差异:
- 主进程仅用来初始化 socket, 并没有创建 mainReactor 来 accept 连接,而由子进程的 Reactor 来 accept 连接
- 通过锁来控制一次只有一个子进程进行 accept( 防止出现惊群现象) , 子进程 accept 新连接后就放到自己的 Reactor 进行处理,不会再分配给其他子进程
## Proactor模式
**Reactor 和 Proactor 的区别**
- **Reactor 是非阻塞同步网络模式,感知的是就绪可读写事件**
- 在每次感知到有事件发生(比如可读就绪事件)后,就需要应用进程主动调用 `read` 方法来完成数据的读取,也就是要应用进程主动将 `socket` 接收缓存中的数据读到应用进程内存中,这个过程是同步的,读取完数据后应用进程才能处理数据
- 简单理解:**来了事件**(有新连接、有数据可读、有数据可写)**操作系统通知应用进程,让应用进程来处理**(从驱动读取到内核以及从内核读取到用户空间)
- **Proactor 是异步网络模式, 感知的是已完成的读写事件**
- 在发起异步读写请求时,需要传入数据缓冲区的地址(用来存放结果数据)等信息,这样系统内核才可以自动帮我们把数据的读写工作完成,这里的读写工作全程由操作系统来做,并不需要像 Reactor 那样还需要应用进程主动发起 read/write 来读写数据,操作系统完成读写工作后,就会通知应用进程直接处理数据
- 简单理解:**来了事件**(有新连接、有数据可读、有数据可写)**操作系统来处理**(从驱动读取到内核,从内核读取到用户空间), **处理完再通知应用进程**
无论是 Reactor, 还是 Proactor, 都是一种基于「事件分发」的网络编程模式, 区别在于 **Reactor 模式是基于「待完成」的 I/O 事件,而 Proactor 模式则是基于「已完成」的 I/O 事件** 。
Proactor 模式的示意图如下:

**工作流程**
- Proactor Initiator 负责创建 Proactor 和 Handler 对象,并将 Proactor 和 Handler 都通过
- Asynchronous Operation Processor 注册到内核
- Asynchronous Operation Processor 负责处理注册请求,并处理 I/O 操作;
- Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor
- Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理
- Handler 完成业务处理
**平台支持**
- **Linux** :在 `Linux` 下的 `异步I/O` 是不完善的,`aio` 系列函数是由 `POSIX` 定义的异步操作接口,不是真正的操作系统级别支持的,而是在用户空间模拟出来的异步。并且仅仅支持基于本地文件的 `aio` 异步操作,网络编程中的 `socket` 是不支持的,这也使得基于 `Linux` 的高性能网络程序都是使用 `Reactor` 方案
- **Windows** :在 `Windows` 下实现了一套完整的支持 `socket` 的异步编程接口,这套接口就是 `IOCP` ,是由操作系统级别实现的 `异步I/O` ,真正意义上 `异步I/O` ,因此在 `Windows` 里实现高性能网络程序可以使用效率更高的 `Proactor` 方案
## select/poll/epoll
select/poll/epoll对比:

**注意**: **遍历**相当于查看所有的位置,**回调**相当于查看对应的位置。
### select

POSIX所规定, 目前几乎在所有的平台上支持, 其良好跨平台支持也是它的一个优点, 本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理
**缺点**
- 单个进程可监视的fd数量被限制, 即能监听端口的数量有限,数值存在如下文件里:`cat /proc/sys/fs/file-max`
- 对socket是线性扫描, 即采用轮询的方法, 效率较低
- select采取了内存拷贝方法来实现内核将FD消息通知给用户空间, 这样一个用来存放大量fd的数据结构, 这样会使得用户空间和内核空间在传递该结构时复制开销大
select是第一版IO复用, 提出后暴漏了很多问题。
- select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的
- select 如果任何一个sock(I/O stream)出现了数据, select 仅仅会返回, 但不会告诉是那个sock上有数据, 只能自己遍历查找
- select 只能监视1024个链接
- select 不是线程安全的, 如果你把一个sock加入到select, 然后突然另外一个线程发现这个sock不用, 要收回, 这个select 不支持的
### poll

本质上和select没有区别, 它将用户传入的数组拷贝到内核空间, 然后查询每个fd对应的设备状态
- 其没有最大连接数的限制,原因是它是基于链表来存储的
- 大量的fd的数组被整体复制于用户态和内核地址空间之间, 而不管这样的复制是不是有意义
- poll特点是“水平触发”, 如果报告了fd后, 没有被处理, 那么下次poll时会再次报告该fd
- 边缘触发: 只通知一次, epoll用的就是边缘触发
poll 修复了 select 的很多问题:
- poll 去掉了1024个链接的限制
- poll 从设计上来说不再修改传入数组
但是poll仍然不是线程安全的, 这就意味着不管服务器有多强悍,你也只能在一个线程里面处理一组 I/O 流。你当然可以拿多进程来配合了,不过然后你就有了多进程的各种问题。
### epoll

在Linux2.6内核中提出的select和poll的增强版本
- 支持水平触发和边缘触发, 最大的特点在于边缘触发, 它只告诉进程哪些fd刚刚变为就绪态, 并且只会通知一次
- 使用“事件”的就绪通知方式, 通过epoll_ctl注册fd, 一旦该fd就绪, 内核就会采用类似callback的回调机制来激活该fd, epoll_wait便可以收到通知
**优点**
- 没有最大并发连接的限制: 能打开的FD的上限远大于1024(1G的内存能监听约10万个端口)
- 效率提升: 非轮询的方式, 不会随着FD数目的增加而效率下降; 只有活跃可用的FD才会调用callback函数, 即epoll最大的优点就在于它只管理“活跃”的连接, 而跟连接总数无关
- 内存拷贝, 利用mmap()文件映射内存加速与内核空间的消息传递; 即epoll使用mmap减少复制开销
- 文件映射内存直接通过地址空间访问,效率更高,把文件映射到内存中
epoll 可以说是 I/O 多路复用最新的一个实现, epoll 修复了poll 和select绝大部分问题, 比如:
- epoll 现在是线程安全的
- epoll 现在不仅告诉你sock组里面数据, 还会告诉你具体哪个sock有数据, 你不用自己去找了
- epoll 内核态管理了各种IO文件描述符, 以前用户态发送所有文件描述符到内核态, 然后内核态负责筛选返回可用数组, 现在epoll模式下所有文件描述符在内核态有存, 查询时不用传文件描述符进去了
## BIO(同步阻塞I/O)
用户需要等待read将socket中的数据读取到buffer后, 才继续处理接收的数据。整个IO请求的过程中, 用户线程是被阻塞的, 这导致用户在发起IO请求时, 不能做任何事情, 对CPU的资源利用率不够。

**特点:**I/O执行的两个阶段进程都是阻塞的。
**优点**
- 能够及时的返回数据,无延迟
- 程序简单, 进程挂起基本不会消耗CPU时间
**缺点**
- I/O等待对性能影响较大
- 每个连接需要独立的一个进程/线程处理, 当并发请求量较大时为了维护程序, 内存、线程和CPU上下文切换开销较大, 因此较少在开发环境中使用
## NIO(同步非阻塞I/O)
用户需要不断地调用read, 尝试读取socket中的数据, 直到读取成功后, 才继续处理接收的数据。整个IO请求过程中, 虽然用户线程每次发起IO请求后可以立即返回, 但为了等到数据, 仍需要不断地轮询、重复请求, 消耗了大量的CPU的资源。

**特点:**non-blocking I/O模式需要不断的主动询问kernel数据是否已准备好。
**优点**
- 进程在等待当前任务完成时, 可以同时执行其他任务进程不会被阻塞在内核等待数据过程, 每次发起的I/O请求会立即返回, 具有较好的实时性
**缺点**
- 不断轮询将占用大量CPU时间, 系统资源利用率大打折扣, 影响性能, 整体数据吞吐量下降
- 该模型不适用web服务器
## IO多路复用(异步阻塞I/O)
通过Reactor的方式, 可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作( 异步) , 而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时, 则通知相应的用户线程( 或执行用户线程的回调函数) , 执行handle_event进行数据读取、处理的工作。

**特点:**通过一种机制能同时等待多个文件描述符, 而这些文件描述符( 套接字描述符) 其中的任意一个变为可读就绪状态, select()/poll()函数就会返回。
**优点**
- 可以基于一个阻塞对象,同时在多个描述符上可读就绪,而不是使用多个线程(每个描述符一个线程),即能处理更多的连接
- 可以节省更多的系统资源
**缺点:**
- 如果处理的连接数不是很多的话, 使用select/poll的web server不一定比使用multi-threading + blocking I/O的web server性能更好
- 可能延迟还更大, 因为处理一个连接数需要发起两次system call
## AIO(异步非阻塞I/O)
AIO(异步非阻塞IO,即NIO.2)。异步IO模型中, 用户线程直接使用内核提供的异步IO API发起read请求, 且发起后立即返回, 继续执行用户线程代码。不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核, 然后操作系统开启独立的内核线程去处理IO操作。当read请求的数据到达时, 由内核负责读取socket中的数据, 并写入用户指定的缓冲区中。最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor, Proactor将IO完成的信息通知给用户线程( 一般通过调用用户线程注册的完成事件处理函数) , 完成异步IO。

**特点:**第一阶段和第二阶段都是有内核完成。
**优点**
- 能充分利用DMA的特性, 将I/O操作与计算重叠, 提高性能、资源利用率与并发能力
**缺点**
- 在程序的实现上比较困难
- 要实现真正的异步 I/O, 操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的异步 I/O。而在 Linux 系统下, Linux 2.6才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编程时都是以 复用式I/O模型为主
## 信号驱动式I/O
信号驱动式I/O是指进程预先告知内核, 使得某个文件描述符上发生了变化时, 内核使用信号通知该进程。在信号驱动式I/O模型, 进程使用socket进行信号驱动I/O, 并建立一个SIGIO信号处理函数, 当进程通过该信号处理函数向内核发起I/O调用时, 内核并没有准备好数据报, 而是返回一个信号给进程, 此时进程可以继续发起其他I/O调用。也就是说, 在第一阶段内核准备数据的过程中, 进程并不会被阻塞, 会继续执行。当数据报准备好之后, 内核会递交SIGIO信号, 通知用户空间的信号处理程序, 数据已准备好; 此时进程会发起recvfrom的系统调用, 这一个阶段与阻塞式I/O无异。也就是说, 在第二阶段内核复制数据到用户空间的过程中, 进程同样是被阻塞的。
**信号驱动式I/O的整个过程图如下: **

**第一阶段(非阻塞):**
- ①: 进程使用socket进行信号驱动I/O, 建立SIGIO信号处理函数, 向内核发起系统调用, 内核在未准备好数据报的情况下返回一个信号给进程, 此时进程可以继续做其他事情
- ②: 内核将磁盘中的数据加载至内核缓冲区完成后, 会递交SIGIO信号给用户空间的信号处理程序
**第二阶段(阻塞):**
- ③: 进程在收到SIGIO信号程序之后, 进程向内核发起系统调用( recvfrom)
- ④: 内核再将内核缓冲区中的数据复制到用户空间中的进程缓冲区中( 真正执行IO过程的阶段) , 直到数据复制完成
- ⑤: 内核返回成功数据处理完成的指令给进程; 进程在收到指令后再对数据包进程处理; 处理完成后, 此时的进程解除不可中断睡眠态, 执行下一个I/O操作
**特点:**借助socket进行信号驱动I/O并建立SIGIO信号处理函数
**优点**
- 线程并没有在第一阶段(数据等待)时被阻塞,提高了资源利用率;
**缺点**
- 在程序的实现上比较困难
- 信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知。信号驱动 I/O 尽管对于处理 UDP 套接字来说有用,即这种信号通知意味着到达一个数据报,或者返回一个异步错误。但是,对于 TCP 而言,信号驱动的 I/O 方式近乎无用,因为导致这种通知的条件为数众多,每一个来进行判别会消耗很大资源,与前几种方式相比优势尽失
**信号通知机制**
- ** 水平触发:**指数据报到内核缓冲区准备好之后, 内核通知进程后, 进程因繁忙未发起recvfrom系统调用; 内核会再次发送通知信号, 循环往复, 直到进程来请求recvfrom系统调用。很明显, 这种方式会频繁消耗过多的系统资源
- ** 边缘触发:**内核只会发送一次通知信号
# Classloader
## JVM类加载机制