bufferunderflowexception 是在 bytebuffer 调用 get() 等读取方法时,position >= limit 导致无数据可读而抛出的异常;常见于未 flip、未检查 hasremaining()、duplicate/slice 后状态误判等场景。

BufferUnderflowException 是什么情况触发的
它不是 Java 运行时自动抛的“空指针式”异常,而是 ByteBuffer 在你主动调用 get()、getInt()、getLong() 等读取方法时,发现当前 position 已经 >= limit,没数据可读了,才直接 throw 的。
常见错误现象:
- 读完一整块数据后,没重置或没检查
hasRemaining(),继续get() - 用
duplicate()或slice()得到子缓冲区,但父缓冲区的position已偏移,子缓冲区实际可读字节数比你以为的少 - 从网络通道读入数据后,忘了调用
flip()就直接读 —— 此时limit还是容量值,position是 0,看似能读,但实际可能只写入了部分字节,flip()后limit才变成真实长度
怎么安全地读取 ByteBuffer 而不崩
核心原则:别假设“我刚写了 N 字节,现在一定能读 N 字节”。NIO 缓冲区的状态(position、limit、capacity)必须显式管理。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 每次读前先用
buffer.hasRemaining()判断,而不是靠 try-catch 拦BufferUnderflowException - 从
SocketChannel.read(buffer)返回值拿到实际读到的字节数后,立刻buffer.flip(),再开始读取逻辑 - 如果要多次读取(比如先读 4 字节长度,再按长度读 payload),记得每读一段就检查
hasRemaining(),别硬算偏移 - 调试时打印状态:
System.out.printf("pos=%d, lim=%d, cap=%d%n", buf.position(), buf.limit(), buf.capacity());
duplicate() / slice() 后为什么更容易出错
这两个方法返回的新 ByteBuffer 共享底层字节数组,但它们的 position 和 limit 是独立的 —— 而且初始值继承自原缓冲区当时的 position 和 limit,不是从 0 开始。
典型陷阱:
-
slice()返回的缓冲区capacity()= 原缓冲区remaining(),但它的position总是 0;如果你在 slice 前没flip(),remaining()可能是 0,导致 slice 出来一个空缓冲区 -
duplicate()复制的是当前状态,包括可能已经走到末尾的position;后续对副本调get()会直接崩 - 用
asIntBuffer()等视图时,底层仍是同一段内存,但单位长度变了(int 是 4 字节),position按“元素数”算,容易和字节偏移混淆
遇到 BufferUnderflowException 该怎么定位
堆栈里看到 java.nio.BufferUnderflowException,说明问题不在 IO 或线程,就在你当前这行 getXXX() 调用上。
排查步骤:
- 加一行
if (!buf.hasRemaining()) throw new IllegalStateException("buffer exhausted at " + buf.position());放在出问题的get()前,让失败更早、信息更明确 - 不要依赖
buffer.array()打印内容 —— 如果是 direct buffer 或只读 buffer,会抛UnsupportedOperationException或ReadOnlyBufferException - 如果是 Netty 或其他框架封装的
ByteBuf,注意它和 JDKByteBuffer行为不同,别混用判断逻辑
最常被忽略的一点:BufferUnderflowException 本身不带上下文,但它永远指向“你试图从已耗尽的缓冲区读数据”这个事实 —— 问题不在异常本身,而在你没在读之前确认缓冲区是否还有余量。










