转载:http://calvin1978.blogcn.com/articles/netty-leak.html
引用计数器的必要性
在内存池的场景中,ByteBuf不再使用时需要主动归还给内存池;
引用计数器相关知识
- ByteBuf被创建时,引用计数的初始值为1;
- 调用release(),计数器减1,当计数器等于零时, deallocate()被调用,回收资源;
- 调用release()或者retain()方法时,如果引用计数器为负数,直接抛出IllegalReferenceCountException异常;
- 计数器基于AtomicIntegerFieldUpdater实现。 不直接用AtomicInteger的原因是ByteBuf对象很多,如果都把int包一层AtomicInteger花销较大,而AtomicIntegerFieldUpdater只需要一个全局的静态变量。
Release原则
在C/C++中,malloc和free是成对出现的,而在Netty里,因为Handler链的存在,ByteBuf经常要传递到下一个Hanlder,所以Release规则变成了:由最后使用ByteBuf的Handler负责释放。 另外,需要注意的是如果在Handler中出现异常,ByteBuf没有成功传递到下一个Hanlder,需要有当前Handler进行释放。
接受消息
ByteBuf分配:在AbstractNioByteChannel.NioByteUnsafe.read() 处创建ByteBuf(引用计数为1),调用 pipeline.fireChannelRead(byteBuf) 送入Handler链。
Handler链处理:
根据谁最后使用谁负责释放的原则,每个Handler对消息可能有三种处理方式:
- 对原消息不做处理,调用 ctx.fireChannelRead(msg)把原消息往下传,那不用做什么释放;
- 将原消息转化为新的消息并调用 ctx.fireChannelRead(newMsg)往下传,那必须把原消息release掉;
- 如果已经不再调用ctx.fireChannelRead(msg)传递任何消息,那更要把原消息release掉;
发送消息
ByteBuf分配: 待发送消息由应用所创建,并调用 ctx.writeAndFlush(msg) 进入Handler链。
Handler链处理: 在每个Handler中的处理类似InBound Message,最后消息会来到HeadHandler,再经过一轮复杂的调用,在flush完成后Netty会将其release掉。
异常处理时的释放
大于0的判断: 在多层异常处理机制中,有些异常处理的地方不一定准确知道ByteBuf之前释放了没有,可以在释放前加上引用计数大于0的判断避免抛出异常;
彻底释放: 有时候不清楚ByteBuf被引用了多少次,但又必须在此进行彻底的释放,可以循环调用reelase()直到返回true。