跳到正文
Elaine Blog
返回

BIO、NIO、直接内存与零拷贝

网络与 I/O

BIO 和 NIO 不是“低级与高级”的区别,而是线程等待方式和数据访问抽象不同。选择前要先知道连接数量、单连接活跃度和业务代码是否阻塞。

Table of contents

Open Table of contents

运行两种文件复制

下载文件传输样例下载。测试分别使用直接 ByteBufferFileChannel.transferTo 复制同一文件:

mvn -Dtest=NetworkServerLabTest#copiesAFileThroughBothDataPaths test

测试验证字节数和内容,不宣称一次小文件运行能证明性能。可靠基准需要预热、多轮测量、足够大的数据,并区分页缓存命中与真实磁盘 I/O。

BIO:阻塞是一种等待语义

传统 InputStream.read() 在没有数据时通常阻塞当前线程。代码直观,很适合连接数量可控、每个连接持续活跃,或配合虚拟线程的场景。 阻塞并不等于浪费 CPU:等待线程会被挂起;代价来自线程资源、调度以及下游没有截止时间。

NIO:Channel、Buffer 与非阻塞

Java NIO 用 **Channel(通道)**表示可读写连接,用 **Buffer(缓冲区)**保存待处理数据。flip() 把缓冲区从写入模式切到读取模式, clear() 则准备下一轮写入。忘记切换是初学者最常见的空读或数据覆盖原因。

SocketChannel 可配置为非阻塞:调用暂时不能完成时立即返回。程序再用 Selector 关注大量 Channel 的就绪事件。非阻塞只覆盖网络事件循环; 如果处理器里执行慢 SQL,事件循环仍会被阻塞。

堆缓冲区和直接缓冲区

堆缓冲区由 JVM 堆管理。直接内存位于 Java 堆外,操作系统 I/O 可以减少中间复制,但分配和回收更昂贵,也受单独的直接内存上限约束。 高性能框架通常池化直接缓冲区,而不是为每条消息调用 allocateDirect

堆外不等于“JVM 管不到”:Java 对象仍持有缓冲区引用,泄漏会表现为进程内存上涨或 OutOfMemoryError: Direct buffer memory

零拷贝到底省了什么

FileChannel.transferTo 尝试让文件数据直接传到目标通道,减少数据进入用户态 Java 缓冲区的次数。“零拷贝”是工程简称,不保证硬件和内核中绝对零次复制, 也不保证一次调用传完全部数据。样例循环检查返回值和当前位置。

Oracle 的 FileChannel API明确说明: transferTo 可能少于请求数量,非阻塞目标暂时没有空间时尤其如此。

选择规则

下一步

阅读select、poll 与 epoll,理解 Selector 背后的操作系统事件通知模型。


分享这篇文章:

上一篇
TCP、UDP、拥塞控制与连接故障
下一篇
select、poll 与 epoll:从就绪通知理解高并发 I/O