Buffer 是 JVM 堆外内存的视图封装,不能直接 new 因其为抽象类,须用 allocate() 或 allocateDirect() 创建;所有 Buffer 共享 capacity、limit、position、mark 四状态,flip() 和 clear() 顺序错误会导致数据不可见。

Buffer 是什么,为什么不能直接 new?
Buffer 不是普通容器,它是 JVM 堆外内存的视图封装,本质是一段可读写的、有状态的字节数组切片。直接 new ByteBuffer() 会报错,因为它是抽象类;必须用 ByteBuffer.allocate()(堆内)或 ByteBuffer.allocateDirect()(堆外)创建。
- 堆内 Buffer 分配快、GC 可回收,但每次调用
channel.read()时可能触发额外拷贝(JVM 到 native) - 堆外 Buffer 避免拷贝、适合高频 IO,但分配慢、不归 GC 管,
cleaner回收不可控,容易内存泄漏 - 所有 Buffer 共享四个关键位置:capacity、limit、position、mark,
flip()和clear()的调用顺序错了,数据就“看不见”了——这是最常卡住人的点
Channel 和传统 Stream 的根本区别在哪?
Channel 是双向的、可非阻塞的、支持 scatter/gather 的底层数据通道;而 InputStream/OutputStream 是单向、阻塞、面向字节流的抽象。你不能把 FileInputStream 当作 Channel 用,但可以用 getChannel() 从 FileInputStream 或 RandomAccessFile 获取一个 FileChannel。
-
SocketChannel和ServerSocketChannel才支持configureBlocking(false),FileChannel不支持非阻塞模式(JDK 一直没实现) - 写入前必须确保 Buffer 处于“可读”状态(
flip()),否则channel.write(buffer)写的是 position 到 limit 之间的内容,可能为 0 字节 -
read()返回值是实际读到的字节数,-1 表示 EOF,0 表示当前无数据(非阻塞下常见),不是错误
Selector 怎么注册 Channel?为什么 register() 必须在非阻塞模式下?
Selector 本身不干活,它只是轮询多个 Channel 的就绪状态。注册前必须调用 channel.configureBlocking(false),否则抛 IllegalBlockingModeException。这是因为 Selector 依赖操作系统级的多路复用机制(epoll/kqueue/select),它们只识别 non-blocking fd。
- 一个 Channel 只能注册到一个 Selector,但一个 Selector 可管理成百上千个 Channel
-
SelectionKey.OP_READ、OP_WRITE、OP_CONNECT、OP_ACCEPT是位掩码,可按需组合,比如OP_READ | OP_WRITE - 每次
select()返回后,必须遍历selectedKeys()并手动remove(),否则下次select()还会返回同一个 key——这个漏掉就会导致 CPU 空转
Buffer、Channel、Selector 一起用时最容易崩在哪?
不是语法错,而是状态错:Buffer 的 position/limit 混乱 + Channel 的阻塞状态误判 + Selector 的 key 未清理,三者叠加会让程序看起来“卡住”或“丢数据”。比如 SocketChannel.write() 返回 0,你以为要重试,其实是因为 Buffer 还在“写模式”,没 flip();又比如 Selector.select() 返回 1,你只处理了一个 key 却忘了 iterator.remove(),下一轮立刻再触发——这种问题不会抛异常,只会让逻辑静默失效。
立即学习“Java免费学习笔记(深入)”;
真正难的从来不是 API 调用,而是时刻对齐这三者的生命周期和状态机。Buffer 有 4 个指针,Channel 有阻塞/非阻塞开关,Selector 有 key 的有效性和就绪性,三者任意一个脱节,IO 就开始“假装工作”。










