2024-04-04  阅读(4)
版权声明:本文为博主付费文章,严禁任何形式的转载和摘抄,维权必究。 本文链接:https://www.skjava.com/mianshi/baodian/detail/7066971601

回答

在 Netty 中,对象池技术是 Netty 的性能优化技术,它是通过重用对象来减少对象创建和销毁的开销,从而提高应用程序的性能和 GC 的频率。

ByteBuf 是 Netty 中数据的载体,Netty 对它采用了对象池技术。ByteBuf 分为池化和非池化两种 ByteBuf。其中池化的ByteBuf可以重用内存空间,从而减少了内存分配和垃圾回收的压力。当请求一个新的ByteBuf时,Netty会首先检查池中是否有可用的 ByteBuf 对象,如果有,则直接返回一个现成的对象;如果没有,则创建一个新对象并在使用后将其返回池中。

Netty的对象池技术主要依赖于Recycler类。Recycler 在 Netty 中是一个轻量级的对象池,借助 Recycler,Netty 可以完成对对象的重复使用。当 Netty 中的一个对象(如ByteBuf)不再使用了,它可以被“回收”到Recycler中,而不是被垃圾回收器回收。当需要一个新对象时,可以从Recycler中获取一个已经存在的实例并重用它,而不是创建一个新的实例。

Recycler 的底层设计有点儿复杂,详细情况阅读下面的【扩展】部分。

扩展

Recycler 快速上手

对象池(英语:Object Pool Pattern)是一种设计模式。一个对象池包含一组已经初始化过且可以使用的对象,可以在有需求时创建和销毁对象。池的使用对象可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非直接销毁它。

Recycler 是 Netty 提供的自定义的轻量级的对象池,借助 Recycler 我们可以完成对 Java 对象的重复使用,下面大明哥就演示如何使用 Recycler。

@NoArgsConstructor
public class User {
    private String name;

    private Integer age;

    private Recycler.Handle<User> handler;

    public User(Recycler.Handle<User> handler) {
        this.handler = handler;
    }

    public Recycler.Handle<User> getHandler() {
        return handler;
    }
}

public class RecyclerTest {

    private static final Recycler<User> USER_RECYCLER = new Recycler<User>() {
        @Override
        protected User newObject(Handle<User> handle) {
            // 新建对象
            return new User(handle);
        }
    };

    public static void main(String[] args) {
        // 从对象池中获取对象
        User user1 = USER_RECYCLER.get();
        System.out.println(user1);

        // 从对象池中获取对象
        User user2 = USER_RECYCLER.get();
        System.out.println(user2);

        // 回收对象 user1
        user1.getHandler().recycle(user1);

        // 再次对象池中获取对象
        User user3 = USER_RECYCLER.get();
        System.out.println(user3);
        
        System.out.println("user1 == user3:" + (user1 == user3));
    }
}

在该实例中我们先从对象池中获取两个对象 user1、user2,由于这个时候 Recycler 中并没有对象,所以它会调用 Recycler 的 newObject() 创建一个新的 User 对象,然后我们再对对象 user1 进行回收,最后再从 Recycler 获取对象 user3,理论上来说 user1 == user3,我们看运行结果:

com.sike.netty.jinjie.bytebuf.recycler.User@36f0f1be
com.sike.netty.jinjie.bytebuf.recycler.User@157632c9
com.sike.netty.jinjie.bytebuf.recycler.User@36f0f1be
user1 == user3:true

运行结果与我们预想的一模一样。

从上面示例我们可以看出,利用 Recycler 我们可以实现对象的回收和再利用。所以我们可以将其当做一个工具类在项目中使用,当然并不是所有场景都可以使用它,我们应该结合实际情况。一般来说符合如下条件,可以使用对象池:

  1. 对象创建开销大,且可以重复使用。我们要注意:并不是所有对象都可以重复利用的
  2. 对象池的数量需要控制在可以接受的范围内,不能够无限膨胀。

复杂的老版本设计

我们先看 4.1.71 版本以前的设计,目前网上几乎所有 Recycler 的分析文章都是基于 4.1.71 之前的版本,所以大明哥先对之前的版本的设计做一个详细介绍,然后再介绍新版本,看看两者之间的实现差异。

我们先看它的内部结构图:

从图中可以看出它有三个核心组件:

  • Stack
  • DefaultHandle
  • WeakOrderQueue

下面大明哥一一介绍这些组件。

  • Stack

Stack 是整个对象池的顶层数据结构,它描述了整个对象池的构造,是 Netty 对象池最核心的类,内部定义了对象池组结构以及获取、回收的场景。为了避免多线程场景下的锁竞争问题,Stack 与 Recycle 内部的 FastThreadLocal 绑定,这样每个线程都拥有自己的 Stack 对象,创造一个无锁的环境。

我们看其定义:

private static final class Stack<T> {

    // 所属 Recycler
    final Recycler<T> parent;
      
    /**
     * 所属线程的弱引用
     * 我们将线程存储在一个 WeakReference 中,否则我们可能是唯一一个在线程死亡后仍然持有对线程本身的强引用的引用,因为 DefaultHandle 将持有对栈的引用。
     * 最大的问题是,如果我们不使用 WeakReference,那么如果用户在某个地方存储了对 DefaultHandle 的引用,
     * 并且永远不会清除这个引用(或者不能及时清除它) ,那么 Thread 可能根本不能被收集。
     */
    final WeakReference<Thread> threadRef;

    // 异线程回收对象时,其他线程能保存的被回收对象的最大个数
    final AtomicInteger availableSharedCapacity;

    // 一个线程可同时缓存多少个 Stack 对象。这个 Stack 可以理解为其他线程的 Stack
    // 默认值: 8
    private final int maxDelayedQueues;

    // 对象池的最大大小,默认最大为 4k
    private final int maxCapacity;

    // 控制对象的回收比率,默认只回收 1/8 的对象
    private final int interval;

    // 存储缓存数据的数组。默认值: 256
    DefaultHandle<?>[] elements;

    // 数组中非空元素数量。默认值: 0
    int size;

    // 跳过回收对象数量。从0开始计数,每跳过一个回收对象+1。
    // 当handleRecycleCount>interval时重置handleRecycleCount=0
    // 默认值: 8。初始值和「interval」,以便第一个元素能被回收
    private int handleRecycleCount;

    // 与异线程回收的相关三个节点
    // WeakOrderQueue 链表的三个重要节点
    private WeakOrderQueue cursor, prev;
    private volatile WeakOrderQueue head;

    // ...
}