Netty源码解析——Buffer之ByteBuf 内存泄漏检测

Netty源码解析——Buffer之ByteBuf 内存泄漏检测

0.引用计数器基础知识

1)  计数器基于AtomicIntegerFieldUpdater,因为ByteBuf对象很多,如果都把int包一层AtomicInteger花销较大,而AtomicIntegerFieldUpdater只需要一个全局的静态变量。所有的ByteBuf的引用计数器初始值为1.

private static final AtomicIntegerFieldUpdater<AbstractReferenceCounted> refCntUpdater =

       AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCounted.class, "refCnt");

private volatile int refCnt = 1;

拓展:AtomicIntegerFieldUpdater:允许类中里面一个字段具有原子性。这个字段必须是volatile类型的,在线程间共享变量时保证其可见性.字段描述类型是与调用者与操作对象字段的关系一致,可以进行反射进行原子操作.对于父类的字段,子类不能直接操作,只能是实例变量和可修改的变量,不能再被修饰的类前面加 static 和final 的修饰符. 比如refCnt不能加static和final修饰符,如果是包装类Integer和Long的化可以使用AtomicReferenceFieldUpdater类.

2)  release()和retain() 分别对应着计数器refCnt加1和减1.等于零时deallocate()被调用进行回收.由于每次操作只能操作的数为1,居然在程序里面直接判断old值为1时就进行deallocate(),为什么这么写?当引用计数器为0时,底下的buffer已经回收,即使ByteBuf对象还在,对它的各种访问操作都会抛出异常。

3)  有duplicate(),slice(),order()所衍生出来的ByteBuf与原对象共享底下的buffer,也共享引用计数器,所以它们经常需要调用retain()来显示自己的存在。copy()方法生成的ByteBuf完全独立于原ByteBuf,而slice()和duplicate()方法生成的ByteBuf与原ByteBuf共享相同的底层实现,但是各自维护独立的索引和标记.slice()方法从原始的ByteBuf中截取一段,这段数据从readerIndex到writeIndex,返回时新的ByteBuf的最大容量maxCapacity为原始ByteBuf的readableBytes().而duplicate()则是把整个ByteBuf截取出来.在一个函数体里面,只要增加了引用计数就必须调用release()方法.

1.Netty内存泄漏检测

内存泄漏,主要是针对池化的ByteBuf.ByteBuf对象被JVM GC掉之前,没有调用release()把底下的DirectByteBuffer或byte[]归还到池里,会导致池越来越大.Netty默认会从分配的ByteBuf里抽样出大约1%的来进行跟踪。如果泄漏,会有如下语句打印:

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

禁用(DISABLED) - 完全禁止泄露检测,省点消耗。

简单(SIMPLE) - 默认等级,告诉我们取样的1%的ByteBuf是否发生了泄露,但总共一次只打印一次,看不到就没有了。

高级(ADVANCED) - 告诉我们取样的1%的ByteBuf发生泄露的地方。每种类型的泄漏(创建的地方与访问路径一致)只打印一次。对性能有影响。

偏执(PARANOID) - 跟高级选项类似,但此选项检测所有ByteBuf,而不仅仅是取样的那1%。对性能有绝大的影响。

    ByteBufAllocator 可用于创建 ByteBuf 对象。创建的过程中,它会调用 #toLeakAwareBuffer(...) 方法,将 ByteBuf 装饰成 LeakAware ( 可检测内存泄露 )的 ByteBuf 对象.

1.1 ResourceLeakDetector

ResourceLeakDetector 为了检测内存是否泄漏,使用了 WeakReference( 弱引用 )和 ReferenceQueue( 引用队列 ),过程如下:

根据检测级别和采样率的设置,在需要时为需要检测的 ByteBuf 创建WeakReference 引用。

当 JVM 回收掉 ByteBuf 对象时,JVM 会将 WeakReference 放入ReferenceQueue 队列中。

通过对 ReferenceQueue 中 WeakReference 的检查,判断在 GC 前是否有释放ByteBuf 的资源,就可以知道是否有资源释放。

private final ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks = PlatformDependent.newConcurrentHashMap();//DefaultResourceLeak集合

private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();//引用队列

private final ConcurrentMap<String, Boolean> reportedLeaks = PlatformDependent.newConcurrentHashMap();//已汇报的内存泄露的资源类型的集合

private final String resourceType;//资源类型

private final int samplingInterval;//采样频率

1.2 ResourceLeakTracker

每个资源( 例如:ByteBuf 对象 ),会创建一个追踪它是否内存泄露的 ResourceLeakTracker 对象,代码如下

leak = AbstractByteBuf.leakDetector.track(buf);

if (leak != null) {

   buf = new AdvancedLeakAwareByteBuf(buf, leak);

}

1.3  DefaultResourceLeak

DefaultResourceLeak 继承 java.lang.ref.WeakReference 类,实现 ResourceLeakTracker 接口,默认 ResourceLeakTracker 实现类。同时,它是 ResourceLeakDetector 内部静态类.

private static final class DefaultResourceLeak<T>

       extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak

构造函数

DefaultResourceLeak(

       Object referent,

       ReferenceQueue<Object> refQueue,

       ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks) {

   super(referent, refQueue);

   trackedHash = System.identityHashCode(referent);

   allLeaks.put(this, LeakEntry.INSTANCE);

   // Create a new Record so we always have the creation stacktrace included.

   headUpdater.set(this, new Record(Record.BOTTOM));

   this.allLeaks = allLeaks;

}

[拓展]:引用队列(Reference Queue)

一旦弱引用对象开始返回null,该弱引用指向的对象就被标记成了垃圾。而这个弱引用对象(非其指向的对象)就没有什么用了。通常这时候需要进行一些清理工作。比如WeakHashMap会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。

引用队列可以很容易地实现跟踪不需要的引用。当你在构造WeakReference时传入一个ReferenceQueue对象,当该引用指向的对象被标记为垃圾的时候,这个引用对象会自动地加入到引用队列里面。接下来,你就可以在固定的周期,处理传入的引用队列,比如做一些清理工作来处理这些没有用的引用对象。

当referent为 ByteBuf 对象时,如果它被正确的释放,即调用了release()方法,从而调用了 AbstractReferenceCountedByteBuf 的closeLeak()方法,最终调用到 ResourceLeakTracker#close(trackedByteBuf)方法,那么该 ByteBuf 对象对应的 ResourceLeakTracker 对象,将从ResourceLeakDetector.allLeaks中移除。在ResourceLeakDetector#reportLeak()方法中,即使从refQueue队列中,获取到该 ByteBuf 对象对应 ResourceLeakTracker 对象,因为在ResourceLeakDetector.allLeaks中移除了,所以在ResourceLeakDetector#reportLeak()!ref.dispose() = true,continue 就不报告内存泄漏了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容