title | date | categories | tags | description | ||||
---|---|---|---|---|---|---|---|---|
22.2.ReferenceCounted |
2020-04-09 |
|
|
|
public class ServerSimpleHandler2 extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { // PooledUnsafeDirectByteBuf(freed)
System.out.println(ReferenceCountUtil.refCnt(msg) + " " + msg.refCnt());
System.out.println(ReferenceCountUtil.refCnt(msg) + " " + msg.refCnt());
// new Thread(new Runnable() {
// @Override
// public void run() {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
//
// }
// System.out.println(ReferenceCountUtil.refCnt(msg) + " " + msg.refCnt());
//
// //下面的两行代码都会报错:Exception in thread "Thread-1" io.netty.util.IllegalReferenceCountException: refCnt: 0
// //就是因为 msg 已经被释放了。虽然断点看的时候, msg里面的一些属性还是有值的, 但是调用就报错。
// //long l = msg.memoryAddress();
// //byte b = msg.readByte();
//
// //客户端不会收到, 并且报错:io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1,并且客户端与客户端的连接channel 会被自动关闭了
// ctx.writeAndFlush(msg);
// }
// }).start();
// 不管是采用注释里面写的方法, 延时 两秒发送, 主要为了等finally调用:ReferenceCountUtil.release(msg); 完成
// 还是下面的直接调用 ctx.write(msg); 都会报错。
// 因为write 是异步的, 调用的时候, msg底层的ByteBuf可能已经被释放掉了。
// 调用下面这一行代码,客户端能收到, 但是报错:io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1, 与客户端连接的channel 没有被关闭
ctx.writeAndFlush(msg);
}
}
ReferenceCountUtil 的refCnt 方法, 内部也是使用msg.refCnt 来实现的
/**
* Returns reference count of a {@link ReferenceCounted} object. If object is not type of
* {@link ReferenceCounted}, {@code -1} is returned.
*/
public static int refCnt(Object msg) {
return msg instanceof ReferenceCounted ? ((ReferenceCounted) msg).refCnt() : -1;
}
// Value might not equal "real" reference count, all access should be via the updater
@SuppressWarnings("unused")
private volatile int refCnt = updater.initialValue();
注意:ByteBuf有个refCnt()方法, 还有个 refCnt 属性。这个refCnt属性是不准确的。
ByteBuf 是因为 实现了 ReferenceCounted这个接口,才有 refCnt()方法
/**
* A reference-counted object that requires explicit deallocation.
ReferenceCounted 是需要显式释放的引用计数的对象。
* <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.
当一个 ReferenceCounted对象被实例化之后, 这个对象的引用计数会被初始化为1.
retain方法用于增加引用计数, relaease用于减少引用计数
如果这个对象的引用计数减少到0, 那么这个对象就会被显示的回收了, 后续访问被回收的对象通常会导致访问冲突。就是出错了。
我的理解:这个引用计数就是Netty自己实现的一套对象管理的工具。
Netty自己帮我们维护一个引用计数。如果引用计数为0,就代表在Netty层面, 对象被释放了。不管Java的垃圾回收机制到底把内存回收了没有,反正在Netty层面是认为它已经不能再使用了。
当我们使用这个对象的时候,Netty首先会帮我们检查 这个对象的引用计数是不是0,如果是,那么好了, 就认为在Netty层面该对象已经被释放,就直接抛出异常了IllegalReferenceCountException。
这样就能解释了上面我们代码里面
// System.out.println(ReferenceCountUtil.refCnt(msg) + " " + msg.refCnt()); // 打印 0 0
// 当msg的引用计数为0的时候,虽然看断点中 msg里面的一些属性还是有值的, 但是调用方法就报错。
// //long l = msg.memoryAddress();
// //byte b = msg.readByte();
* </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.
如果一个容器对象(比如List) 实现了ReferenceCounted,并且它所包含的元素也都实现了ReferenceCounted, 那么, 当这个容器的 引用计数变成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);
}
上面 调用 memoryAddress 报错的原因:实际是调用 PooledUnsafeDirectByteBuf 的 memoryAddress。是返回了直接内存的内存地址,就是个long型的。
@Override
public long memoryAddress() {
ensureAccessible();
return memoryAddress;
}
ensureAccessible();
/**
* 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);
}
}
isAccessible
/**
* Used internally by {@link AbstractByteBuf#ensureAccessible()} to try to guard
* against using the buffer after it was released (best-effort).
*/
boolean isAccessible() {
return refCnt() != 0;
}
真相了, 这里判断了 refCnt() 这个方法的返回值是否是0, 如果是0, 就代表不允许访问了。
到这里, 我也大概明白了 ReferenceCounted