您当前的位置: 首页 > 

Netty 如何实现内存泄露的追踪审计

蔚1 发布时间:2019-09-25 23:31:22 ,浏览量:3

Netty 中使用内存池化技术有效的提升了程序的整体性能。但是由于回归了手动申请内存和释放的方式,在粗心或者遗忘的情况下,开发者往往会忘记释放申请得到的内存而造成内存泄露。而在复杂的程序中寻找泄露的内存对象也往往不容易。为此 Netty 提供了针对资源对象的追踪审计功能,帮助开发者发现潜在的内存泄露问题。

在本场 Chat 中,会讲到如下内容:

  1. Netty 如何监控资源对象的可能泄露
  2. Netty 中对泄露追踪的代码实现解读

适合人员:对 Netty 内部实现细节感兴趣的同学

本文已参加 GitChat「我的技术实践」有奖征文活动,活动链接:[ GitChat「我的技术实践」有奖征文活动](https://gitbook.cn/gitchat/activity/5d5e3d9d877aaf2

前言

一般而言,在 Netty 程序中都会采用池化的 ByteBuf,也就是PooledByteBuf以提高程序性能。但是PooledByteBuf需要在使用完毕后手工释放,否则就会因为PooledByteBuf申请的内存空间没有归还进而造成内存泄露,最终 OOM。而一旦泄露发生,在复杂的应用程序中找到未手工释放的ByteBuf并不是一个简单的活计,在没有工具辅助的情况只能白盒检查所有源码,效率无疑十分低下。

为了解决这个问题,Netty 设计了专门的泄露检测接口用于实现对需要手动释放的资源对象的监控。

JDK 的弱引用和引用队列

在分析 Netty 的泄露监控功能之前,先来复习下其中会用到的 JDK 知识:引用。

在 Java 中存在 4 中引用类型,分别是强引用,软引用,弱引用,虚引用。

强引用

强引用,是我们写程序最经常使用的方式。比如一个将一个值赋给一个变量,那这个对象值就被该变量强引用了。除非设置为 null,否则 java 的内存回收不会回收该对象。就算是内存不足异常发生也不会。

软引用

软引用所引用的对象会在 java 内存不足的时候,被 gc 回收。如果 gc 发生的时候,java 的内存还充足则不会回收这个对象 使用的方式如下

  • SoftReference ref = new SoftReference(new Date());
  • Date tmp = ref.get(); //如果对象没有被回收,则这个 get 操作会返回初始化的值。如果被回收了之后,则返回 null
弱引用

弱引用则比软引用更差一些。只要是 gc 发生的时候,弱引用的对象都会被回收。使用方式上和软引用类似,如下

  • WeakReference re = new WeakReference(new Date());
  • re.get();
虚引用

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在 java 中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

除了强引用之外,其余的引用都有一个引用队列可以与之配合。当 java 清理调用不必要的引用后,会将这个引用本身(不是引用指向的值对象)添加到队列之中。代码如下

ReferenceQueue queue = new ReferenceQueue();WeakReference re = new WeakReference(new Date(), queue);Reference> allLeaks;//记录追踪对象的 hash 值,用于后续操作中的对象对比。private final int trackedHash;

这个类的作用有三个:

  1. 调用 record 方法记录调用轨迹
  2. 调用 close 方法结束追踪
  3. 以及本身作为WeakReference,在追踪对象被 GC 回收后自身被入列到ReferenceQueue中。

先来看下record方法,代码如下

@Overridepublic void record() {    record0(null);    }@Overridepublic void record(Object hint) {    record0(hint);}private void record0(Object hint) {            if (TARGET_RECORDS > 0) {                Record oldHead;                Record prevHead;                Record newHead;                boolean dropped;                do {                    if ((prevHead = oldHead = headUpdater.get(this)) == null) {                        // already closed.                        return;                    }                    final int numElements = oldHead.pos + 1;                    if (numElements >= TARGET_RECORDS) {                        final int backOffFactor = Math.min(numElements - TARGET_RECORDS, 30);                        if (dropped = PlatformDependent.threadLocalRandom().nextInt(1             
关注
打赏
1688896170
查看更多评论
0.1562s