从 Netty 版本 4 开始,某些对象的生命周期由它们的引用计数来管理,因此一旦不再使用,Netty 就可以将它们(或与之关联的共享资源)返回到对象池(或是对象分配器)。因为垃圾回收和引用队列不能提供对有效和实时不可达性的保证,所以Netty采用了引用计数这一替代机制 —— 但这样带来的代价是一定程度提升了编程的复杂度,某种意义上来说相当于内存释放是交由使用者管理了,虽然在一些场景下Netty有提供封装好的自动释放方法和逻辑,但手动释放的情况完全存在,所以在Netty中写代码时无法避免的需要保持对内存释放的潜意识 。
Netty中很多这样的引用计数对象,比如
DatagramPacket
,HttpContent
,WebSocketframe
,ByteBuf
他们的共性都是实现了ReferenceCounted 接口, 其中 ByteBuf 是最值得注意和最常用的类型
package io.netty.util;
/**
* A reference-counted object that requires explicit deallocation.
* <p>
* When a new {@link ReferenceCounted} is instantiated, it starts with the reference count of {@code 1}.
* {@link #retain()} increases the reference count, and {@link #release()} decreases the reference count.
* If the reference count is decreased to {@code 0}, the object will be deallocated explicitly, and accessing
* the deallocated object will usually result in an access violation.
* </p>
* <p>
* If an object that implements {@link ReferenceCounted} is a container of other objects that implement
* {@link ReferenceCounted}, the contained objects will also be released via {@link #release()} when the container's
* reference count becomes 0.
* </p>
*/
public interface ReferenceCounted {
/**
* Returns the reference count of this object. If {@code 0}, it means this object has been deallocated.
*/
int refCnt();
/**
* Increases the reference count by {@code 1}.
*/
ReferenceCounted retain();
/**
* Increases the reference count by the specified {@code increment}.
*/
ReferenceCounted retain(int increment);
/**
* Records the current access location of this object for debugging purposes.
* If this object is determined to be leaked, the information recorded by this operation will be provided to you
* via {@link ResourceLeakDetector}. This method is a shortcut to {@link #touch(Object) touch(null)}.
*/
ReferenceCounted touch();
/**
* Records the current access location of this object with an additional arbitrary information for debugging
* purposes. If this object is determined to be leaked, the information recorded by this operation will be
* provided to you via {@link ResourceLeakDetector}.
*/
ReferenceCounted touch(Object hint);
/**
* Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at
* {@code 0}.
*
* @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
*/
boolean release();
/**
* Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference
* count reaches at {@code 0}.
*
* @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
*/
boolean release(int decrement);
}
引用计数对象
举例:引用计数对象的初始引用计数为 1
ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
当释放引用计数对象时,其引用计数将减少 1。如果引用计数达到 0,则引用计数的对象将被释放或返回到它来自的对象池:
assert buf.refCnt() == 1;
// PS: 只有当引用计数变为 0 时,release() 才返回 true。
boolean destroyed = buf.release();
assert destroyed;
assert buf.refCnt() == 0;
如果尝试访问引用计数为 0 的引用计数对象将触发 IllegalReferenceCountException:
assert buf.refCnt() == 0;
try {
buf.writeLong(0xdeadbeef);
throw new Error("should not reach here");
} catch (IllegalReferenceCountExeception e) {
// 异常内容
}
维持引用
只要在引用计数对象未被销毁的情况下,引用计数也可以通过 retain() 操作递增(每次调用都会+1),:
ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
buf.retain();
assert buf.refCnt() == 2;
boolean destroyed = buf.release();
assert !destroyed;
assert buf.refCnt() == 1;
派生缓冲
像 ByteBuf.duplicate()、ByteBuf.slice() 和 ByteBuf.order(ByteOrder) 这些方法会创建一个派生缓冲区,这样的缓冲区会共享父缓冲区的内存区域。派生缓冲区没有自己的引用计数,而是共享父缓冲区的引用计数 —— 因此,如果要将派生缓冲区传递给应用程序的其他组件,则必须首先在这之上调用 retain()。
ByteBuf parent = ctx.alloc().directBuffer();
ByteBuf derived = parent.duplicate();
// 创建派生缓冲区不会增加引用计数。
assert parent.refCnt() == 1;
assert derived.refCnt() == 1;
相比之下,ByteBuf.copy() 和 ByteBuf.readBytes(int) 产生的Buf 不是派生缓冲区,它们返回的 ByteBuf 属于一个新分配的缓冲区。
ByteBuf parent = ctx.alloc().directBuffer(512);
parent.writeBytes(...);
try {
while (parent.isReadable(16)) {
ByteBuf derived = parent.readSlice(16);
derived.retain();
process(derived);
}
} finally {
parent.release();
}
...
public void process(ByteBuf buf) {
...
buf.release();
}
文章评论