简介

为什么世界上有这么多JAVA的程序员呢?其中一个很重要的原因就是JAVA相对于C++而言,不需要考虑对象的释放,一切都是由垃圾回收器来完成的。在崇尚简单的现代编程世界中,会C++的高手越来越少,会JAVA的程序员越来越多。

JVM的垃圾回收器中一个很重要的概念就是Reference count,也就是对象的引用计数,用来控制对象是否还被引用,是否可以被垃圾回收。

netty也是运行在JVM中的,所以JVM中的对象引用计数也适用于netty中的对象。这里我们说的对象引用指的是netty中特定的某些对象,通过对象的引用计数来判断这些对象是否还被使用,如果不再被使用的话就可以把它们(或它们的共享资源)返回到对象池(或对象分配器)。

这就叫做netty的对象引用计数技术,其中一个最关键的对象就是ByteBuf。

ByteBuf和ReferenceCounted

netty中的对象引用计数是从4.X版本开始的,ByteBuf是其中最终要的一个应用,它利用引用计数来提高分配和释放性能.

先来看一下ByteBuf的定义:

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf>

可以看到ByteBuf是一个抽象类,它实现了ReferenceCounted的接口。

ReferenceCounted就是netty中对象引用的基础,它定义了下面几个非常重要的方法,如下所示:

int refCnt();

ReferenceCounted retain();

ReferenceCounted retain(int increment);

boolean release();

boolean release(int decrement);

其中refCnt返回的是当前引用个数,retain用来增加引用,而release用来释放引用。

ByteBuf的基本使用

刚分配情况下ByteBuf的引用个数是1:

ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;

当调用他的release方法之后,refCnt就变成了0:

boolean destroyed = buf.release();
assert destroyed;
assert buf.refCnt() == 0;

当调用它的retain方法,refCnt就会加一:

ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
buf.retain();
assert buf.refCnt() == 2;

要注意的是,如果ByteBuf的refCnt已经是0了,就表示这个ByteBuf准备被回收了,如果再调用其retain方法,则会抛出IllegalReferenceCountException:refCnt: 0, increment: 1

所以我们必须在ByteBuf还未被回收之前调用retain方法。

既然refCnt=0的情况下,不能调用retain()方法,那么其他的方法能够调用吗?

我们来尝试调用一下writeByte方法:

        try {
buf.writeByte(10);
} catch (IllegalReferenceCountException e) {
log.error(e.getMessage(),e);
}

可以看到,如果refCnt=0的时候,调用它的writeByte方法会抛出IllegalReferenceCountException异常。

这样看来,只要refCnt=0,说明这个对象已经被回收了,不能够再使用了。

ByteBuf的回收

既然ByteBuf中保存的有refCnt,那么谁来负责ByteBuf的回收呢?

netty的原则是谁消费ByteBuf,谁就负责ByteBuf的回收工作。

在实际的工作中,ByteBuf会在channel中进行传输,根据谁消费谁负责销毁的原则,接收ByteBuf的一方,如果消费了ByteBuf,则需要将其回收。

这里的回收指的是调用ByteBuf的release()方法。

ByteBuf的衍生方法

ByteBuf可以从一个parent buff中衍生出很多子buff。这些子buff并没有自己的reference count,它们的引用计数是和parent buff共享的,这些提供衍生buff的方法有:ByteBuf.duplicate(), ByteBuf.slice() 和 ByteBuf.order(ByteOrder)。

buf = directBuffer();
ByteBuf derived = buf.duplicate();
assert buf.refCnt() == 1;
assert derived.refCnt() == 1;

因为衍生的byteBuf和parent buff共享引用计数,所以如果要将衍生的byteBuf传给其他的流程进行处理的话,需要调用retain()方法:

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();
}

ChannelHandler中的引用计数

netty根据是读消息还是写消息,可以分为InboundChannelHandler和OutboundChannelHandler,分别用来读消息和写消息。

根据谁消费,谁释放的原则,对Inbound消息来说,读取完毕之后,需要调用ByteBuf的release方法:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
try {
...
} finally {
buf.release();
}
}

但是如果你只是将byteBuf重发到channel中供其他的步骤进行处理,则不需要release:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
...
ctx.fireChannelRead(buf);
}

同样的在Outbound中,如果只是简单的重发,则不需要release:

public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {
System.err.println("Writing: " + message);
ctx.write(message, promise);
}

如果是处理了消息,则需要release:

public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {
if (message instanceof HttpContent) {
// Transform HttpContent to ByteBuf.
HttpContent content = (HttpContent) message;
try {
ByteBuf transformed = ctx.alloc().buffer();
....
ctx.write(transformed, promise);
} finally {
content.release();
}
} else {
// Pass non-HttpContent through.
ctx.write(message, promise);
}
}

内存泄露

因为reference count是netty自身来进行维护的,需要在程序中手动进行release,这样会带来一个问题就是内存泄露。因为所有的reference都是由程序自己来控制的,而不是由JVM来控制,所以可能因为程序员个人的原因导致某些对象reference count无法清零。

为了解决这个问题,默认情况下,netty会选择1%的buffer allocations样本来检测他们是否存在内存泄露的情况.

如果发生泄露,则会得到下面的日志:

LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetectionLevel=advanced' or call ResourceLeakDetector.setLevel()

上面提到了一个检测内存泄露的level,netty提供了4种level,分别是:

  • DISABLED---禁用泄露检测
  • SIMPLE --默认的检测方式,占用1% 的buff。
  • ADVANCED - 也是1%的buff进行检测,不过这个选项会展示更多的泄露信息。
  • PARANOID - 检测所有的buff。

具体的检测选项如下:

java -Dio.netty.leakDetection.level=advanced ...

总结

掌握了netty中的引用计数,就掌握了netty的财富密码!

本文的例子可以参考:learn-netty4

本文已收录于 http://www.flydean.com/43-netty-reference-cound/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

netty系列之:JVM中的Reference count原来netty中也有的更多相关文章

  1. netty系列之:不用怀疑,netty中的ByteBuf就是比JAVA中的好用

    目录 简介 ByteBuf和ByteBuffer的可扩展性 不同的使用方法 性能上的不同 总结 简介 netty作为一个优秀的的NIO框架,被广泛应用于各种服务器和框架中.同样是NIO,netty所依 ...

  2. netty系列之:netty中常用的对象编码解码器

    目录 简介 什么是序列化 重构序列化对象 序列化不是加密 使用真正的加密 使用代理 Serializable和Externalizable的区别 netty中对象的传输 ObjectEncoder O ...

  3. netty系列之:在netty中使用protobuf协议

    目录 简介 定义protobuf 定义handler 设置ChannelPipeline 构建client和server端并运行 总结 简介 netty中有很多适配不同协议的编码工具,对于流行的goo ...

  4. netty系列之:channel,ServerChannel和netty中的实现

    目录 简介 channel和ServerChannel netty中channel的实现 AbstractChannel和AbstractServerChannel LocalChannel和Loca ...

  5. netty系列之:EventExecutor,EventExecutorGroup和netty中的实现

    目录 简介 EventExecutorGroup EventExecutor EventExecutorGroup在netty中的基本实现 EventExecutor在netty中的基本实现 总结 简 ...

  6. netty系列之:netty中的核心MessageToByte编码器

    目录 简介 MessageToByte框架简介 MessageToByteEncoder ByteToMessageDecoder ByteToMessageCodec 总结 简介 之前的文章中,我们 ...

  7. netty系列之:netty中的核心编码器bytes数组

    目录 简介 byte是什么 netty中的byte数组的工具类 netty中byte的编码器 总结 简介 我们知道netty中数据传输的核心是ByteBuf,ByteBuf提供了多种数据读写的方法,包 ...

  8. netty系列之:netty中的核心解码器json

    目录 简介 java中对json的支持 netty对json的解码 总结 简介 程序和程序之间的数据传输方式有很多,可以通过二进制协议来传输,比较流行的像是thrift协议或者google的proto ...

  9. netty系列之:netty中的自动解码器ReplayingDecoder

    目录 简介 ByteToMessageDecoder可能遇到的问题 ReplayingDecoder的实现原理 总结 简介 netty提供了一个从ByteBuf到用户自定义的message的解码器叫做 ...

随机推荐

  1. linux 三剑客(持续更新)排版后续再说,边学边记笔记

    切记:seq命令用于产生从某个数到另外一个数之间的所有整数.sed才是处理文本的命令 在遇到扩展符号时,需要添加特定参数,| () +[] 为扩展符号时,必须添加参数 egrep/grep -E  s ...

  2. test_5 排序‘+’、‘-’

    题目是:有一组"+"和"-"符号,要求将"+"排到左边,"-"排到右边,写出具体的实现方法. 方法一: l=['-', ...

  3. 在 python 项目中如何记录日志

    一. 概述 写本文的目的是我在写 python 项目的时候需要记录日志,我忘记怎么处理了,每次都需要去网上查一遍,好记性不如烂笔头, 这里把查阅的内容记录下来,方便以后查找. python 项目中记录 ...

  4. Solon 开发,三、构建一个Bean的三种方式

    Solon 开发 一.注入或手动获取配置 二.注入或手动获取Bean 三.构建一个Bean的三种方式 四.Bean 扫描的三种方式 五.切面与环绕拦截 六.提取Bean的函数进行定制开发 七.自定义注 ...

  5. leeetcode 20. 有效的括号

    20. 有效的括号 问题描述 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足: 左括号必须用相同类型的右括号闭合. 左括号必须以正确的 ...

  6. JS隐形,显性,名义和鸭子类型

    隐形转换 JavaScript中只有在一些极少数的情况下才会因为一个类型错误抛出错误.例如:调用非函数对象或者获取null / underfined的属性时,这就是隐形转换. 首先JS在遇到运算符的时 ...

  7. vue组件实现图片的拖拽和缩放

    vue实现一个组件其实很简单但是要写出一个好的可复用的组件那就需要多学习和钻研一下,一个好的组件必须有其必不可少的有优点:一是能提高应用开发效率.测试性.复用性等:二是组件应该是高内聚.低耦合的:三是 ...

  8. SQL查询中关键字的执行顺序

    SQL语句中的每个关键字都按照顺序往下执行,而每一步操作会生成一个临时表,最后的临时表就是最终结果: FROM <left_table>:from子句返回初始结果集 <join_ty ...

  9. MySQL的学习记录 DAY03~

    小记:昨天打了新冠加强针,今天开始拉肚子,嗓子疼,超级难受~

  10. 五天学完MySQL打卡挑战 day01

    明天继续上传学习笔记和练习的Word文档 晚安~