当前位置: 首页 > 后端技术 > Java

Netty系列:JVM中的引用计数原来netty也有

时间:2023-04-01 22:12:52 Java

简介为什么世界上有那么多JAVA程序员?其中一个很重要的原因就是JAVA相比C++不需要考虑对象的释放,一切都由垃圾回收器完成。在崇尚简单的现代编程世界里,懂C++的高手越来越少,懂JAVA的程序员越来越多。JVM的垃圾回收器中有一个很重要的概念就是Referencecount,也就是对象的引用计数,用来控制对象是否还被引用,是否可以被垃圾回收。Netty也是运行在JVM中的,所以JVM中的对象引用计数也适用于netty中的对象。我们这里所说的对象引用,是指netty中的一些具体对象。通过对象的引用计数来判断这些对象是否还在被使用。如果它们不再被使用,它们(或它们的共享资源)可以返回到对象池(或对象分配器)。这就是netty的对象引用计数技术,其中最关键的一个对象就是ByteBuf。ByteBuf和ReferenceCountednetty中的对象引用计数从4.X版本开始,ByteBuf是终极应用之一。它使用引用计数来提高分配和释放性能。我们先看下ByteBuf的定义:publicabstractclassByteBufimplementsReferenceCounted,Comparable可以看到ByteBuf是一个实现了ReferenceCounted接口的抽象类。ReferenceCounted是netty中对象引用的基础。它定义了以下几个非常重要的方法,如下:intrefCnt();ReferenceCountedretain();ReferenceCountedretain(intincrement);booleanrelease();booleanrelease(intdecrement);其中refCnt返回当前引用数,retain用于增加引用,release用于释放引用。ByteBuf的基本使用ByteBuf刚分配时的引用数为1:ByteBufbuf=ctx.alloc().directBuffer();assertbuf.refCnt()==1;调用它的release方法后,refCnt变为Became0:booleandestroyed=buf.release();assertdestroyed;assertbuf.refCnt()==0;当调用它的retain方法时,refCnt会添加一个:ByteBufbuf=ctx.alloc().directBuffer();assertbuf.refCnt()==1;buf.retain();assertbuf.refCnt()==2;需要注意的是,如果ByteBuf的refCnt已经是0,说明ByteBuf准备好被回收了,如果再调用retain方法,会抛出IllegalReferenceCountException:refCnt:0,increment:1,所以我们必须在回收ByteBuf之前调用retain方法。既然refCnt=0的时候不能调用retain()方法,那能不能调用其他方法呢?让我们尝试调用writeByte方法:try{buf.writeByte(10);}catch(IllegalReferenceCountExceptione){log.error(e.getMessage(),e);可以看到如果refCnt=0,调用它的writeByte方法会抛出IllegalReferenceCountException。从这个角度来看,只要refCnt=0,就说明这个对象已经被回收了,不能再使用了。ByteBuf的回收既然refCnt是存放在ByteBuf中的,那么谁来负责ByteBuf的回收呢?netty的原则是谁消费了ByteBuf,谁负责ByteBuf的回收。在实际工作中,ByteBuf会在通道中传输。根据谁消费谁销毁的原则,接收ByteBuf的一方如果消费了ByteBuf就需要回收。这里的回收指的是调用ByteBuf的release()方法。ByteBuf的派生方式ByteBuf可以从一个parentbuff中派生出很多子buff。这些子buff没有自己的引用计数,它们的引用计数与父buff共享。提供派生buff的方法有:ByteBuf.duplicate()、ByteBuf.slice()和ByteBuf.order(ByteOrder)。buf=directBuffer();ByteBufderived=buf.duplicate();断言buf.refCnt()==1;断言derived.refCnt()==1;因为派生的byteBuf和parentbuff共享引用计数,所以如果想把派生的byteBuf传递给其他进程处理的话,需要调用retain()方法:ByteBufparent=ctx.alloc().directBuffer(512);parent.writeBytes(...);try{while(parent.isReadable(16)){ByteBufderived=parent.readSlice(16);derived.retain();过程(派生);}}finally{parent.release();}...publicvoidprocess(ByteBufbuf){...buf.release();}ChannelHandler中的引用计数netty根据是否读取可以分为InboundChannelHandler和OutboundChannelHandler或写消息,分别用于读取和写入消息。根据谁消费谁释放的原则,对于Inbound消息,在读取之后,需要调用ByteBuf的释放方法:publicvoidchannelRead(ChannelHandlerContextctx,Objectmsg){尝试{..。}最后{buf.release();但是如果只是将byteBuf重新发送到channel进行其他步骤的处理,就不需要release了:...ctx.fireChannelRead(buf);}同样,在Outbound中,如果只是简单的重传,不需要释放:publicvoidwrite(ChannelHandlerContextctx,Objectmessage,ChannelPromisepromise){System.err.println("写作:“+消息);ctx.write(message,promise);}如果消息被处理,需要释放:publicvoidwrite(ChannelHandlerContextctx,Objectmessage,ChannelPromisepromise){if(messageinstanceofHttpContent){//TransformHttpContenttoByteBuf.HttpContent内容=(HttpContent)消息;try{ByteBuftransformed=ctx.alloc().buffer();....ctx.write(转录变形,承诺);}最后{content.release();}}else{//传递非HttpContentthrough.ctx.write(message,promise);}}内存泄漏因为引用计数是netty自己维护的,需要在程序中手动释放,会造成内存泄漏的问题。因为所有的引用都是由程序自己控制的,而不是由JVM控制的,所以有些对象引用可能是程序员个人原因造成的。无法清除计数。为了解决这个问题,默认情况下,netty会选择1%的bufferallocations样本来检测它们是否存在内存泄漏。如果发生泄漏,将获得以下日志:LEAK:ByteBuf.release()wasnotcalledbeforeit'sgarbage-collected。启用高级泄漏报告以查明发生泄漏的位置。要启用高级泄漏报告,指定JVM选项'-Dio.netty.leakDetectionLevel=advanced'或调用上面提到的ResourceLeakDetector.setLevel()检测内存泄漏级别,netty提供4个级别,即:DISABLED---禁用泄漏检测SIMPLE---默认检测方式,占buff的1%。高级-也检测到1%增益,但此选项将显示更多泄漏信息。偏执狂-检测所有增益。具体检测选项如下:java-Dio.netty.leakDetection.level=advanced...总结掌握了netty中的引用计数,就掌握了netty的财富密码!本文示例可参考:learn-netty4本文已收录于http://www.flydean.com/43-netty-reference-cound/最通俗的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!