说说 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+ 小伙伴加入学习,欢迎点击围观
面试考察点
- 基础掌握度:知道 Netty 有对象池机制,也清楚它解决什么问题(GC 压力)。
- 核心原理:能讲明白 Recycler 的工作机制(ThreadLocal 缓存 + 异线程回收)。
- 源码深度:能说清楚 Stack、WeakOrderQueue、DefaultHandle、DELAYED_RECYCLED 这几个核心组件是怎么协作的。
- 实战意识:了解对象池的使用场景和踩坑点。
核心答案
Netty 搞对象池,主要就是想减少高频对象创建/销毁带来的 GC 压力。核心类是 Recycler,一个基于 ThreadLocal 的轻量级对象池,设计思路就八个字:“同线程回收 + 异线程转移”。
简单说就是:每个线程维护自己的对象栈(Stack),自己用完的对象直接放回自己的栈里;其他线程回收的对象先放到一个共享队列(WeakOrderQueue),等需要的时候再异步转移回来。
深度解析
一、为什么需要对象池
Netty 是做网络 IO 的框架,处理请求时会频繁创建/销毁对象:
ByteBufChannelHandlerContext- 各种事件对象
如果让 JVM 来回收这些短生命周期对象,会触发频繁的 Minor GC,搞不好还会 Full GC。
对象池的思路很直接:对象用完不扔,放回池子里,下次再用。
二、核心架构
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 | 同步阻塞 |
| 性能 | 极高,无锁化 | 一般 |
| 适用场景 | 网络框架内部高频对象 | 业务层通用对象 |
| 复杂度 | 高 | 低 |
面试高频追问
-
为什么用 FastThreadLocal 而不是 JDK 的 ThreadLocal?
- JDK ThreadLocal 用 hash 查找,存在线性探测和哈希冲突;FastThreadLocal 用数组下标直接访问,O(1)。但要注意必须配合
FastThreadLocalThread才能发挥优势,否则性能退化。
- JDK ThreadLocal 用 hash 查找,存在线性探测和哈希冲突;FastThreadLocal 用数组下标直接访问,O(1)。但要注意必须配合
-
Recycler 的对象如何保证线程安全?
- 同线程操作无并发问题;异线程回收通过 MPSC 队列保证安全。
-
Recycler 历史上有过什么坑?
- 历史版本出过内存泄漏 Bug,社区有详细的分析文章。原因多跟 WeakOrderQueue 的 Link 节点管理不当、对象被回收但没归还到池子有关,排查比较困难。
常见面试变体
- “Netty 的
ByteBuf是怎么复用的?” - “Recycler 和 ThreadLocal 有什么关系?”
- “对象池如何避免内存泄漏?”
记忆口诀
一栈一队列,同线回收直接塞,异线回收走队列:每个线程一个 Stack,一个 WeakOrderQueue 链表;同线程回收直接 push Stack,异线程回收走 WeakOrderQueue,用的时候再转移回来。
总结
Netty 的 Recycler 是一个为高频网络 IO 场景设计的无锁化对象池,核心就两块:ThreadLocal(Stack)+ 异线程回收(WeakOrderQueue)。把“同线程零成本、异线程异步转移”这两条主线搞清楚,Recycler 的核心就懂了。这玩意儿不一定适合业务代码(太重了),但这种“为性能把优化做到极致”的思路,挺值得玩味的。
