Netty从0到1系列之Recycler对象池技术【1】
文章目录
- Recycler 对象池技术【1】
- 一、Recycler概述
- 1.1 概述
- 1.2 Recycler入门程序
- 二、Recycler设计理念【老版本】
- 2.1 概述
- 2.2 三级缓存架构
- 2.2.1 核心数据结构与交互流程
- 2.2.2 关键组件解析【老版本】
- 2.2.3 Recycler各个组件的关系
推荐阅读:
【01】Netty从0到1系列之I/O模型
【02】Netty从0到1系列之NIO
【03】Netty从0到1系列之Selector
【04】Netty从0到1系列之Channel
【05】Netty从0到1系列之Buffer(上)
【06】Netty从0到1系列之Buffer(下)
【07】Netty从0到1系列之零拷贝技术
【08】Netty从0到1系列之整体架构、入门程序
【09】Netty从0到1系列之EventLoop
【10】Netty从0到1系列之EventLoopGroup
【11】Netty从0到1系列之Future
【12】Netty从0到1系列之Promise
【13】Netty从0到1系列之Netty Channel
【14】Netty从0到1系列之ChannelFuture
【15】Netty从0到1系列之CloseFuture
【16】Netty从0到1系列之Netty Handler
【17】Netty从0到1系列之Netty Pipeline【上】
【18】Netty从0到1系列之Netty Pipeline【下】
【19】Netty从0到1系列之Netty ByteBuf【上】
【20】Netty从0到1系列之Netty ByteBuf【中】
【21】Netty从0到1系列之Netty ByteBuf【下】
【22】Netty从0到1系列之Netty 逻辑架构【上】
【23】Netty从0到1系列之Netty 逻辑架构【下】
【24】Netty从0到1系列之Netty 启动细节分析
【25】Netty从0到1系列之Netty 线程模型【上】
【26】Netty从0到1系列之Netty 线程模型【下】
【27】Netty从0到1系列之Netty ChannelPipeline
【28】Netty从0到1系列之Netty ChannelHandler
【29】Netty从0到1系列之Netty拆包、粘包【1】
【30】Netty从0到1系列之Netty拆包、粘包【2】
【31】Netty从0到1系列之Netty拆包、粘包【3】
【32】Netty从0到1系列之Netty拆包、粘包【4】
【33】Netty从0到1系列之Netty拆包、粘包【5】
【34】Netty从0到1系列之动态从内存分配】
【35】Netty从0到1系列之writeAndFlush原理分析】
【36】Netty从0到1系列之Netty内存管理【上】】
【37】Netty从0到1系列之Netty内存管理【下】】
Recycler 对象池技术【1】
一、Recycler概述
1.1 概述
在网络编程中,频繁地创建和销毁短生命周期的对象(如 ByteBuf
、 ChannelHandlerContext
等)会产生巨大的 GC 压力。为了解决这个问题,对象池(Object Pool)是一种常见的优化模式。Netty 没有直接使用通用的对象池库(如 Apache Commons Pool),而是自研了一个高度优化、线程感知的轻量级对象池 —— Recycler
。
它的核心设计目标是:
- 线程隔离: 大多数对象的获取和回收都在同一个线程内完成,避免多线程竞争。
- 无锁化: 通过巧妙的数据结构设计,在大多数情况下实现无锁操作。
- 弹性伸缩: 池的大小可以根据负载动态调整,防止内存泄漏。
Recycler的核心思想是:对象使用完毕后不立即销毁,而是放回池中,供后续重复使用。
1.2 Recycler入门程序
✅ 1. 定义实体类
/*** @author: laoren* @description: TODO* @version: 1.0.0*/
@Data
public class User {private Integer id;private String username;private String password;private String email;private Recycler.Handle<User> handle;public User(Recycler.Handle<User> handle) {this.handle = handle;}public void recycle() {handle.recycle(this);}
}
✅ 2. 缓存相关
/*** @author: laoren* @description: TODO* @version: 1.0.0*/
public class UserCache {public static final Recycler<User> USER_RECYCLER = new Recycler<>() {@Overrideprotected User newObject(Handle<User> handle) {return new User(handle);}};
}
✅ 3. 测试
package cn.tcmeta.recycler.demo01;/*** @author: laoren* @description: TODO* @version: 1.0.0*/
public class T1 {public static void main(String[] args) {User user = UserCache.USER_RECYCLER.get();user.setUsername("张小三");user.recycle();User user1 = UserCache.USER_RECYCLER.get();System.out.println(user1.getUsername());System.out.println(user == user1);}
}
代码示例中定义了对象池实例 USER_RECYCLER,其中实现了 newObject() 方法,如果对象池没有可用的对象,会调用该方法新建对象。
此外需要创建 Recycler.Handle 对象与 User 对象进行绑定,这样我们就可以通过 userRecycler.get() 从对象池中获取 User 对象,如果对象不再使用,通过调用 User 类实现的 recycle() 方法即可完成回收对象到对象池.
对象池与内存池的都是为了提高 Netty 的并发处理能力,我们知道 Java 中频繁地创建和销毁对象的开销是很大的,所以很多人会将一些通用对象缓存起来,当需要某个对象时,优先从对象池中获取对象实例
。
通过重用对象,不仅避免频繁地创建和销毁所带来的性能损耗,而且对 JVM GC 是友好的,这就是对象池的作用。
二、Recycler设计理念【老版本】
2.1 概述
对象池与内存池的都是为了提高 Netty 的并发处理能力,我们知道 Java 中频繁地创建和销毁对象的开销是很大的,所以很多人会将一些通用对象缓存起来,当需要某个对象时,优先从对象池中获取对象实例。
通过重用对象,不仅避免频繁地创建和销毁所带来的性能损耗,而且对 JVM GC 是友好的,这就是对象池的作用。
Recycler 是 Netty 提供的自定义实现的轻量级对象回收站,借助 Recycler 可以完成对象的获取和回收。既然 Recycler 是 Netty 自己实现的对象池,那么它是如何设计的呢?首先看下 Recycler 的内部结构.
2.2 三级缓存架构
Recycler 采用 “线程局部缓存(ThreadLocalCache)→ 栈缓存(Stack)→ 弱引用缓存(WeakOrderQueue)” 的三级缓存架构,结合线程隔离与跨线程转移机制,实现高效对象复用。
2.2.1 核心数据结构与交互流程
2.2.2 关键组件解析【老版本】
✅ 1. ThreadLocalCache: 线程私有入口
- 作用:通过
ThreadLocal
为每个线程维护独立的缓存入口,避免线程竞争,提升访问效率。 - 核心字段:
stack
(当前线程的专属对象栈)、weakOrderQueues
(存储其他线程归还的对象队列)。
✅ 2. Stack: 线程专属对象栈
- 数据结构:基于数组实现的栈(LIFO 顺序),支持快速弹出(复用)和压入(归还)。
- 核心机制:
- 最大容量限制:默认
DEFAULT_MAX_CAPACITY_PER_THREAD = 4096
,避免单个线程缓存过多对象导致内存溢出。 - 对象清理:当线程销毁时,
ThreadLocal
关联的Stack
会被回收,栈中对象若无人引用则会被 GC 清理。
- 最大容量限制:默认
- 每个线程都拥有自己的对象栈(
Stack
)。- 这是对象的主要存储地。当线程需要对象时,优先从自己的栈中弹出(
pop
) - 当对象被回收时,优先压入(
push
)自己的栈。 - 这部分操作是无锁且线程本地的,速度极快。
- 这是对象的主要存储地。当线程需要对象时,优先从自己的栈中弹出(
✅ 3. WeakOrderQueue:跨线程对象转移队列
- 设计初衷:当线程 T2 归还 “属于线程 T1 的对象” 时(如 T2 处理 T1 创建的 ByteBuf),无法直接写入 T1 的
Stack
(避免线程安全问题),因此通过WeakOrderQueue
暂存,由 T1 主动读取。 - 弱引用特性:
WeakOrderQueue
对线程 T1 的引用为弱引用(WeakReference
),当 T1 销毁时,队列会自动被清理,避免内存泄漏。 - 链表结构:每个
WeakOrderQueue
包含多个Link
节点,Link
内部用数组存储对象,支持批量转移。
当某个线程(假设为线程A)回收一个它自己创建的对象时,直接放入自己的 Stack
。但如果一个对象被另一个线程(假设为线程B)回收(例如,在线程B的 EventLoop
中处理 writeAndFlush
完成),Netty 不会让线程B直接操作线程A的 Stack
(这需要加锁)。取而代之的是,线程B会将该对象放入一个为线程A专属的 WeakOrderQueue
中。
- 每个线程的
Stack
会持有多个来自其他线程的WeakOrderQueue
链表。 WeakOrderQueue
内部是一个链表结构,每个节点(Link
)可以存放多个对象。- 当线程A自己的
Stack
为空时,它会遍历这些属于它的WeakOrderQueue
,将其他线程为它回收的对象“转移”到自己的Stack
中。
2.2.3 Recycler各个组件的关系
核心组件是 Stack,Stack 是整个对象池的顶层数据结构,描述了整个对象池的构造,用于存储当前本线程回收的对象。在多线程的场景下,Netty 为了避免锁竞争问题,每个线程都会持有各自的对象池,内部通过 FastThreadLocal 来实现每个线程的私有化。FastThreadLocal 你可以理解为 Java 里的 ThreadLocal.
static final class Stack<T> {final Recycler<T> parent; // 所属的 Recyclerfinal WeakReference<Thread> threadRef; // 所属线程的弱引用final AtomicInteger availableSharedCapacity; // 异线程回收对象时,其他线程能保存的被回收对象的最大个数final int maxDelayedQueues; // WeakOrderQueue最大个数private final int maxCapacity; // 对象池的最大大小,默认最大为 4kprivate final int ratioMask; // 控制对象的回收比率,默认只回收 1/8 的对象private DefaultHandle<?>[] elements; // 存储缓存数据的数组private int size; // 缓存的 DefaultHandle 对象个数private int handleRecycleCount = -1; // WeakOrderQueue 链表的三个重要节点private WeakOrderQueue cursor, prev;private volatile WeakOrderQueue head;// 省略其他代码
}
对应上面 Recycler 的内部结构图,Stack 包用于存储缓存数据的 DefaultHandle 数组,以及维护了 WeakOrderQueue 链表中的三个重要节点,关于 WeakOrderQueue 相关概念我们之后再详细介绍
。
除此之外,Stack 其他的重要属性我在源码中已经全部以注释的形式标出,大部分已经都非常清楚,其中 availableSharedCapacity 是比较难理解的,每个 Stack 会维护一个 WeakOrderQueue 的链表,每个 WeakOrderQueue 节点会保存非当前线程的其他线程所释放的对象,例如图中 ThreadA 表示当前线程,WeakOrderQueue 的链表存储着 ThreadB、ThreadC 等其他线程释放的对象。
availableSharedCapacity 的初始化方式为 new AtomicInteger(max(maxCapacity / maxSharedCapacityFactor, LINK_CAPACITY)),默认大小为 16K,其他线程在回收对象时,最多可以回收 ThreadA 创建的对象个数不能超过 availableSharedCapacity。
还有一个疑问就是既然 Stack 是每个线程私有的,为什么 availableSharedCapacity 还需要用 AtomicInteger 呢?因为 ThreadB、ThreadC 等多个线程可能都会创建 ThreadA 的 WeakOrderQueue,存在同时操作 availableSharedCapacity 的情况。
第二个要介绍的组件是 WeakOrderQueue
,WeakOrderQueue 用于存储其他线程回收到当前线程所分配的对象,并且在合适的时机,Stack 会从异线程的 WeakOrderQueue 中收割对象。如上图所示,ThreadB 回收到 ThreadA 所分配的内存时,就会被放到 ThreadA 的 WeakOrderQueue 当中。
第三个组件是 Link
,每个 WeakOrderQueue 中都包含一个 Link 链表,回收对象都会被存在 Link 链表中的节点上,每个 Link 节点默认存储 16 个对象,当每个 Link 节点存储满了会创建新的 Link 节点放入链表尾部。
第四个组件是 DefaultHandle
,DefaultHandle 实例中保存了实际回收的对象,Stack 和 WeakOrderQueue 都使用 DefaultHandle 存储回收的对象。在 Stack 中包含一个 elements 数组,该数组保存的是 DefaultHandle 实例。DefaultHandle 中每个 Link 节点所存储的 16 个对象也是使用 DefaultHandle 表示的。
介绍完 Recycler 的内存结构,对 Recycler 有了初步的认识。Recycler 作为一个高性能的对象池,在多线程的场景下,Netty 是如何保证 Recycler 高效地分配和回收对象的呢?接下来我们一起看下 Recycler 对象获取和回收的原理。