正确使用 Recycler 需继承并实现 newObject(Handle),传入 Handle 供回收凭证;get 与 recycle 必须同线程成对调用;对象需 reset 清状态;容量配置需匹配线程数,避免池失效。

直接用 Recycler 就行,但必须按 Netty 的约定写法初始化、获取和回收,否则对象不会进池,GC 压力照旧。
Recycler 的正确声明方式:泛型 + newObject() 必须重写
不能只 new 一个空 Recycler,它是个抽象类。你得继承并实现 newObject(Handle,且返回的对象必须持有该 Handle 引用——这是后续能被安全回收的关键。
-
Handle是回收的“凭证”,不传给对象,recycle()调用时会找不到归属栈 - 构造函数里别传
maxCapacityPerThread太大(如 > 4096),线程本地缓存溢出后会降级为无池创建 - 不要在
newObject()里做耗时操作(如 DB 查询、远程调用),它可能被高频调用
示例:
private static final Recycler<mybizrequest> RECYCLER = new Recycler<mybizrequest>() {
@Override
protected MyBizRequest newObject(Handle<mybizrequest> handle) {
return new MyBizRequest(handle); // 必须把 handle 传进去
}
};</mybizrequest></mybizrequest></mybizrequest>
获取与回收必须成对出现,且不能跨线程
Recycler.get() 返回的对象,生命周期内只能由**同一个线程**调用它的 recycle() 方法;跨线程调用会导致对象被丢进 WeakOrderQueue 等待迁移,延迟回收甚至泄漏。
- Netty 不允许你在 IO 线程取对象,扔给业务线程处理完再回收——回收必须发生在取对象的同一线程
- 如果业务逻辑必须异步,要么把对象序列化/拷贝,要么用
Promise或回调封装,在原线程完成回收 - 忘记调用
recycle()是最常见泄漏点:对象没入池,下次还得new,等于白配
回收前要清空对象状态,避免脏数据复用
Recycler 不会自动重置对象字段,复用时若残留上次请求的 ID、时间戳或集合内容,业务逻辑大概率出错。
- 推荐在对象构造器里不做初始化,改在
reset()方法中统一清空(比如id = 0、attrs.clear()) - 在
recycle()被调用前,确保业务代码已执行完reset(),否则下次get()拿到的是“脏”对象 - 不要依赖
finalize()或弱引用清理——Recycler完全绕过 GC 生命周期
注意默认容量和线程数匹配,否则池失效
每个线程的本地栈默认容量是 DEFAULT_MAX_CAPACITY_PER_THREAD = 4096,但总对象数还受全局 MAX_SHARED_CAPACITY_FACTOR 和线程数影响。如果你有 200 个 EventLoop 线程,却只设了 maxCapacityPerThread=32,高频场景下很快就会触发“池满→新建→不回收”循环。
- 观察
Recycler的scavenge()调用频率:太高说明池命中率低,可能容量不足或回收漏了 - 上线前用 JFR 或
-XX:+PrintGCDetails对比开启前后 Young GC 次数,确认是否真降低了分配速率 - 池不是万能的:对象太大(如 > 1MB)或生命周期极长(> 几秒),反而增加栈内存压力,不如直接走堆分配
真正难的不是写对那几行 Recycler 代码,而是让整个调用链路——从 Netty ChannelRead 到业务处理再到 recycle——始终在线程边界内闭环。稍一脱钩,池就形同虚设。










