说说 Netty 的对象池技术?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 基础掌握度:知道 Netty 有对象池机制,也清楚它解决什么问题(GC 压力)。
  2. 核心原理:能讲明白 Recycler 的工作机制(ThreadLocal 缓存 + 异线程回收)。
  3. 源码深度:能说清楚 Stack、WeakOrderQueue、DefaultHandle、DELAYED_RECYCLED 这几个核心组件是怎么协作的。
  4. 实战意识:了解对象池的使用场景和踩坑点。

核心答案

Netty 搞对象池,主要就是想减少高频对象创建/销毁带来的 GC 压力。核心类是 Recycler,一个基于 ThreadLocal 的轻量级对象池,设计思路就八个字:“同线程回收 + 异线程转移”。

简单说就是:每个线程维护自己的对象栈(Stack),自己用完的对象直接放回自己的栈里;其他线程回收的对象先放到一个共享队列(WeakOrderQueue),等需要的时候再异步转移回来。

深度解析

一、为什么需要对象池

Netty 是做网络 IO 的框架,处理请求时会频繁创建/销毁对象:

  • ByteBuf
  • ChannelHandlerContext
  • 各种事件对象

如果让 JVM 来回收这些短生命周期对象,会触发频繁的 Minor GC,搞不好还会 Full GC。

对象池的思路很直接:对象用完不扔,放回池子里,下次再用

二、核心架构

Netty Recycler 对象池核心架构图
Netty Recycler 对象池核心架构图

Recycler 的核心结构,关键就下面这几块:

  • 每个线程一个 Stack:存该线程可以复用的对象,默认每个 Stack 最大容量 32768(2^15),可以通过 -Dio.netty.recycler.maxCapacityPerThread 调整。
  • DefaultHandle:对象的包装器,记录对象所属的 Stack,是对象进出池的基本单位。
  • WeakOrderQueue:每个线程除了自己的 Stack,还维护一个 WeakOrderQueue 链表,专门接收其他线程回收过来的对象。
  • DELAYED_RECYCLED:线程级缓存,类型是 Map<Stack<?>, WeakOrderQueue>,记录当前线程为其他 Stack 回收对象的队列。也就是说线程 B 回收线程 A 的对象时,会从 DELAYED_RECYCLED 里找到对应 Stack A 的 WeakOrderQueue,再把对象塞进去。
  • 同线程回收 O(1):直接 push 到 Stack,没有任何并发开销。
  • 异线程回收:把对象塞到目标 Stack 对应的 WeakOrderQueue 里(MPSC 模式)。
  • 获取对象:先从自己 Stack pop,空了再从 WeakOrderQueue 转移一批过来。

三、关键源码片段

public abstract class Recycler<T> {

    // 每个线程自己的对象栈
    private final FastThreadLocal<Stack<T>> threadLocal =
        new FastThreadLocal<Stack<T>>() {
            @Override
            protected Stack<T> initialValue() {
                return new Stack<>(Recycler.this, Thread.currentThread(),
                    maxCapacityPerThread, maxSharedCapacityFactor);
            }
        };

    // 异线程回收时使用:记录当前线程为其他 Stack 回收对象的队列
    private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>
        DELAYED_RECYCLED = new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {
            @Override
            protected Map<Stack<?>, WeakOrderQueue> initialValue() {
                return new WeakHashMap<>();
            }
        };

    public T get() {
        Stack<T> stack = threadLocal.get();
        DefaultHandle<T> handle = stack.pop();
        if (handle == null) {
            // 池里没有,新建一个
            handle = stack.newHandle();
            handle.value = newObject(handle);
        }
        return (T) handle;
    }
}

使用方式很简单,继承 Recycler 实现 newObject 方法就行:

public class User {
    // 每个类一个 Recycler 实例
    private static final Recycler<User> RECYCLER = new Recycler<User>() {
        @Override
        protected User newObject(Handle<User> handle) {
            return new User(handle);
        }
    };

    private final Handle<User> handle;

    private User(Handle<User> handle) {
        this.handle = handle;
    }

    public static User newInstance() {
        return RECYCLER.get();
    }

    public void recycle() {
        handle.recycle(this);  // 归还到对象池
    }
}

四、几个关键设计点

1. FastThreadLocal 替代 ThreadLocal

Netty 自己实现了 FastThreadLocal,用数组下标替代 hash 查找,访问效率比 JDK 的 ThreadLocal 更高。

注意:必须配合 FastThreadLocalThread 使用才能达到最优性能,如果用的是普通 Thread,FastThreadLocal 会退化到跟 JDK ThreadLocal 差不多的实现。Netty 自己的 EventLoop 用的就是 FastThreadLocalThread,所以能用上这个优化。

2. 异线程回收用 MPSC 队列

线程 B 回收线程 A 创建的对象时,要把对象塞到 A 的 WeakOrderQueue 里。这操作必须是线程安全的,Netty 用了 MPSC(Multi-Producer Single-Consumer)队列来避免锁竞争。这也是 Recycler 性能能拉满的关键之一。

3. WeakReference 防内存泄漏

WeakOrderQueue 对 Stack 的引用是 WeakReference,如果某个线程挂了,它的 Stack 能被 GC 回收,不会一直占着内存。同样,DELAYED_RECYCLED 用的也是 WeakHashMap,key 是 Stack 的弱引用。

4. 容量限制与可禁用

  • 每个 Stack 最大容量默认 32768(通过 -Dio.netty.recycler.maxCapacityPerThread 调整)
  • maxCapacityPerThread == 0 时,直接禁用对象池,返回 NOOP_HANDLE,每次都新建对象,不做回收
  • 这种可配置的设计在排查问题时特别好用,怀疑对象池搞事情就一把关掉

五、与 Apache Commons Pool 对比

维度 Netty Recycler Commons Pool
设计目标 高频小对象复用 通用对象池
实现方式 ThreadLocal + Lock-free 同步阻塞
性能 极高,无锁化 一般
适用场景 网络框架内部高频对象 业务层通用对象
复杂度

面试高频追问

  1. 为什么用 FastThreadLocal 而不是 JDK 的 ThreadLocal?

    • JDK ThreadLocal 用 hash 查找,存在线性探测和哈希冲突;FastThreadLocal 用数组下标直接访问,O(1)。但要注意必须配合 FastThreadLocalThread 才能发挥优势,否则性能退化。
  2. Recycler 的对象如何保证线程安全?

    • 同线程操作无并发问题;异线程回收通过 MPSC 队列保证安全。
  3. Recycler 历史上有过什么坑?

    • 历史版本出过内存泄漏 Bug,社区有详细的分析文章。原因多跟 WeakOrderQueue 的 Link 节点管理不当、对象被回收但没归还到池子有关,排查比较困难。

常见面试变体

  • “Netty 的 ByteBuf 是怎么复用的?”
  • “Recycler 和 ThreadLocal 有什么关系?”
  • “对象池如何避免内存泄漏?”

记忆口诀

一栈一队列,同线回收直接塞,异线回收走队列:每个线程一个 Stack,一个 WeakOrderQueue 链表;同线程回收直接 push Stack,异线程回收走 WeakOrderQueue,用的时候再转移回来。

总结

Netty 的 Recycler 是一个为高频网络 IO 场景设计的无锁化对象池,核心就两块:ThreadLocal(Stack)+ 异线程回收(WeakOrderQueue)。把“同线程零成本、异线程异步转移”这两条主线搞清楚,Recycler 的核心就懂了。这玩意儿不一定适合业务代码(太重了),但这种“为性能把优化做到极致”的思路,挺值得玩味的。