bytebuffer.mark()仅保存当前position值到mark字段,非快照;reset()仅恢复position,clear/flip/compact会清空mark致invalidmarkexception。

ByteBuffer.mark() 不是“保存位置”,而是“打个标记”
很多人误以为 mark() 会复制当前状态或创建快照,其实它只是把 position 的当前值存进一个内部字段 mark(初始为 -1),没有任何缓冲区拷贝。一旦后续调用 reset(),就只是把 position 设回这个值——仅此而已。
这意味着:如果在 mark() 后又调用了 clear()、flip() 或 compact(),mark 值会被丢弃(设为 -1),再调用 reset() 会直接抛出 InvalidMarkException。
网络协议解析中常见错误是:收到一个包后反复 flip() + mark(),结果第二次 mark() 失效,reset() 报错。
- 必须在
flip()后、首次读取前调用mark() - 避免在
mark()后调用任何重置 position/limit 的操作 - 每次试探前检查
buffer.hasRemaining()和buffer.isReadOnly() == false
用 mark/reset 实现协议头多方案试探的典型流程
比如某自定义协议头前 2 字节可能是类型 ID,但不同版本语义不同:0x01 表示带 TLS 扩展,0x02 表示压缩包头,而 0x03 可能是旧版兼容格式。你不能只靠一次读取就决定怎么解析后续字段。
正确做法是:先 mark(),读 2 字节判断类型;若不匹配预期,就 reset() 回头,换另一套逻辑重新读。
buffer.flip();
buffer.mark();
// 尝试解析为新版 TLS 头
if (buffer.getShort() == 0x01) {
parseTlsHeader(buffer);
} else {
buffer.reset(); // 回退到 mark 位置(即 position=0)
if (buffer.getShort() == 0x02) {
parseCompressedHeader(buffer);
} else {
buffer.reset();
parseLegacyHeader(buffer);
}
}
注意:这里每轮 getShort() 都推进 position,所以 reset() 是必需的;否则第二轮读的就是第 3–4 字节了。
mark/reset 在堆外内存(DirectBuffer)和复合缓冲区中是否可靠?
在标准 JDK HeapByteBuffer 和 DirectByteBuffer 中,mark/reset 行为完全一致,只要没被清除就有效。但遇到 CompositeByteBuf(Netty)或包装类(如 WrappedByteBuffer)时,mark() 可能被忽略或未委托到底层。
真实项目中,如果你用的是 Netty,别依赖 mark()/reset() —— 它的 CompositeByteBuf 默认不支持该语义;应改用 slice() + duplicate(),或者显式记录 offset:
- Netty 推荐方式:
buffer.readSlice(2).getShort()(不移动原 buffer position) - 或保存原始
readerIndex,用setReaderIndex()回退 - JDK 原生
ByteBuffer的mark在多线程下非安全,不要跨线程共享并 reset
reset 失败的三个高频原因和对应检查点
当 reset() 抛 InvalidMarkException,不是 bug,而是你忽略了状态约束。重点查这三项:
-
buffer.mark()是否真的执行过?(比如条件分支里漏写了) - 中间是否调用了
buffer.clear()、buffer.flip()或buffer.compact()?它们都会清空mark - 是否在
mark()后写入过数据?(如put()操作会重置mark,即使 buffer 是只读的也会抛异常)
最稳妥的调试方式是每次操作后打印状态:System.out.printf("pos=%d, lim=%d, mark=%d%n", buffer.position(), buffer.limit(), ((Buffer) buffer).mark) —— 注意 mark 是 Buffer 父类字段,需强转。
协议解析越复杂,越容易在嵌套试探中丢失 mark 点。建议对关键 header 解析路径做单元测试,覆盖所有 reset() 分支,尤其验证 position 是否真回到了期望值。










