BIO 和 NIO 不是“低级与高级”的区别,而是线程等待方式和数据访问抽象不同。选择前要先知道连接数量、单连接活跃度和业务代码是否阻塞。
Table of contents
Open Table of contents
运行两种文件复制
下载文件传输样例下载。测试分别使用直接
ByteBuffer 和 FileChannel.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 可能少于请求数量,非阻塞目标暂时没有空间时尤其如此。
选择规则
- 先用最简单、可设截止时间的阻塞模型;连接规模和测量证据要求更高时再引入事件驱动。
- 直接内存适合稳定复用的大量 I/O,不适合用来隐藏堆容量问题。
- 文件到网络的静态传输可评估
transferTo;数据需要业务转换时仍要进入用户态处理。
下一步
阅读select、poll 与 epoll,理解 Selector 背后的操作系统事件通知模型。