netty/netty · critical · IllegalReferenceCountException
refCnt: 0
Error message
refCnt: 0
What it means
Thrown by AbstractByteBuf.ensureAccessible as IllegalReferenceCountException(0) when any buffer-accessing method is called on a buffer whose reference count is zero (it has been release()d). ensureAccessible is the universal guard called at the top of nearly every accessor, gated by the io.netty.buffer.checkAccessible flag (default true). A zero count means the buffer's memory may already be returned to its pool, so all further access is forbidden as a use-after-free defense.
Source
Thrown at buffer/src/main/java/io/netty/buffer/AbstractByteBuf.java:1480
}
}
private void checkReadableBytes0(int minimumReadableBytes) {
ensureAccessible();
if (checkBounds && readerIndex > writerIndex - minimumReadableBytes) {
throw new IndexOutOfBoundsException(String.format(
"readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
readerIndex, minimumReadableBytes, writerIndex, this));
}
}
/**
* Should be called by every method that tries to access the buffers content to check
* if the buffer was released before.
*/
protected final void ensureAccessible() {
if (checkAccessible && !isAccessible()) {
throw new IllegalReferenceCountException(0);
}
}
final void setIndex0(int readerIndex, int writerIndex) {
this.readerIndex = readerIndex;
this.writerIndex = writerIndex;
}
final void discardMarks() {
markedReaderIndex = markedWriterIndex = 0;
}
/**
* Obtain the memory address without checking {@link #ensureAccessible()} first, if possible.
*/
long _memoryAddress() {
return isAccessible() && hasMemoryAddress() ? memoryAddress() : 0L;
}View on GitHub (pinned to 70040aacae)
Solutions
- Call buf.retain() before handing a buffer to code that will release it, if you still need it afterward; release it in your own finally too.
- Track ownership explicitly: assign one owner responsible for release(); use try-with-resources via a wrapper or ReferenceCounted semantics.
- Add buf.refCnt() / ByteBufUtil.isAccessible(buf) assertions in debug to catch double releases early.
- Avoid releasing buffers you did not retain (Netty releases inbound buffers after the pipeline by default).
Example fix
// before buf.release(); ctx.writeAndFlush(buf); // downstream releases again, then: int x = buf.readInt(); // refCnt 0 -> IllegalReferenceCountException // after buf.retain(); ctx.writeAndFlush(buf); // ... later, when truly done: buf.release();
Defensive patterns
Strategy: validation
Validate before calling
// Check accessibility before any access
if (!buf.isAccessible()) {
// re-acquire or skip; do NOT call any getter on a released buffer
return;
}
int v = buf.readInt(); Type guard
static boolean isAlive(ByteBuf buf) {
return buf.refCnt() > 0;
} Try / catch
try {
buf.writeInt(v);
} catch (IllegalReferenceCountException e) {
buf = alloc.buffer(); // re-acquire a live buffer
buf.writeInt(v);
} Prevention
- retain() before handing a buffer to code that will release it.
- Establish one owner responsible for each release().
- Run with -Dio.netty.leakDetection.level=PARANOID in staging to find premature releases.
- Never release inbound buffers you did not retain; the pipeline releases them.
When it happens
Trigger: Calling any getter/reader/writer (or toString, release again) on a buffer after release() was called once too many times, or after a downstream handler released it (e.g. ctx.writeAndFlush released the buffer, then you touch it).
Common situations: Forgetting that writeAndFlush/ChannelPipeline release outbound buffers; releasing a buffer in a finally block AND in a downstream handler (double release); passing a buffer to a codec that retains/releases it while you still hold a reference; autoRead/ref-count bugs in pooled allocators.
Related errors
- refCnt: {refCnt}
- readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex
- writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%
- length(%d) exceeds src.readableBytes(%d) where src is: %s
- length(%d) exceeds dst.writableBytes(%d) where dst is: %s
AI-assisted analysis of netty/netty@70040aacae (2026-08-14).
Data as JSON: /api/errors/f582781726884e49.
Report an issue: GitHub.