Netty Recycler 详解
1. 引言
本文档是对 Netty 中 Recycler 类进行深入分析和总结的详细笔记。Recycler 是一个用于实现对象池的工具类,通过对象池化技术,可以减少对象创建和销毁的开销,提高应用程序的性能。Netty 中针对堆内存和直接内存分别提供了池化实现:PooledHeapByteBuf 和 PooledDirectByteBuf,它们都依赖于 Recycler 来管理对象的生命周期。
2. Recycler 核心方法
Recycler 类主要提供了以下三个核心方法:
get(): 从对象池中获取一个对象实例。如果池中没有可用对象,则会创建一个新的对象。recycle(T, Handle): 将一个使用完毕的对象回收到对象池中。T是对象的泛型类型,Handle是一个用于标识被回收对象的句柄。newObject(Handle): 这是一个抽象方法,由具体的Recycler实现类实现,用于在对象池为空时创建新的对象实例。
3. Recycler UML 图
┌───────────────────┐ ┌─────────────────────┐
│ Recycler<T> │ │ Stack<T> │
├───────────────────┤ ├─────────────────────┤
│ - maxCapacity │ │ - threadRef (WeakRef)│
│ - maxSharedCapacity│<>─────┐ │ - elements (数组) │
│ - ratio │ │ │ - head (WeakOrderQueue)│
└───────────────────┘ │ └─────────────────────┘
| △
| │ 1
| │
| ┌─────────────────────┐
│ │ DefaultHandle<T> │
│ ├─────────────────────┤
│ │ - value (缓存对象) │
│ │ - stack (所属Stack) │
└──>│ - recycleId │
└─────────────────────┘
△ △
│ │
│ │
┌──────────────────┘ └──────────────────┐
│ │
┌─────────────────────┐ ┌─────────────────────┐
│ WeakOrderQueue │ │ Link │
├─────────────────────┤ ├─────────────────────┤
│ - head (Link链表头) │<>─────────────────────>│ - elements (数组) │
│ - next (链表指针) │ 1..n │ - readIndex │
│ - owner (WeakRef) │ └─────────────────────┘
└─────────────────────┘
4. Recycler 关联的核心类
Recycler 的实现依赖于以下四个核心类:
DefaultHandle: 对象的包装类。当对象被缓存在Recycler中时,会被包装成DefaultHandle实例。Handle参数实际上就是DefaultHandle实例。Stack: 存储当前线程回收的对象。每个使用Recycler的线程都会拥有一个独立的Stack实例,该Stack与当前线程绑定(通过ThreadLocal实现)。对象的获取(get())对应Stack的pop()操作,从栈顶弹出一个DefaultHandle;对象的回收(recycle())对应Stack的push()操作,将对象包装成DefaultHandle并压入栈顶。WeakOrderQueue: 用于存储其他线程回收到当前线程Stack的对象。当一个线程从自己的Stack中获取不到对象时,会尝试从WeakOrderQueue中获取。每个线程的Stack维护一个WeakOrderQueue链表,链表中的每个节点对应一个其他线程的WeakOrderQueue,其他线程回收到该Stack的对象会存储在对应的WeakOrderQueue中。Link:WeakOrderQueue内部包含一个Link链表。实际被回收的对象存储在Link链表的某个Link节点里的数组中。当一个Link节点存储的回收对象达到容量上限时,WeakOrderQueue会创建一个新的Link节点并将其添加到Link链表的尾部。
5. Recycler 对象存储结构图
(此处应插入文章中提供的 Recycler.png 对象存储结构图,描述 Stack、WeakOrderQueue 和 Link 之间的组织关系。)
6. 源码分析
6.1.Recycler.recycle(T, Handle) 方法
该方法用于回收一个对象到对象池。
首先判断
handle是否是NOOP_HANDLE(空操作句柄),如果是则直接返回false,表示不进行回收。将
handle强转为DefaultHandle类型。检查
DefaultHandle关联的Stack的parent是否是当前的Recycler实例,如果不是则表示该对象不属于当前对象池,返回false。检查传入的对象
o是否与DefaultHandle中包装的value是同一个对象,如果不是则抛出IllegalArgumentException异常。调用
DefaultHandle的recycle()方法执行真正的回收操作。返回
true,表示回收成功。
6.2.DefaultHandle.recycle() 方法
该方法将 DefaultHandle 对象压入其关联的 Stack 中。
- 直接调用
stack.push(this)方法将当前的DefaultHandle对象压入Stack中。
6.3.Stack.push(DefaultHandle) 方法
该方法将一个 DefaultHandle 对象压入 Stack 中。根据当前线程是否是拥有该 Stack 的线程,分为两种情况处理:
当前线程是拥有该
Stack的线程: 调用pushNow(item)方法,直接将DefaultHandle放入Stack内部的数组中。当前线程不是拥有该
Stack的线程: 调用pushLater(item, currentThread)方法,将DefaultHandle放入该Stack关联的WeakOrderQueue中,以便后续被拥有该Stack的线程回收。
6.3.1.Stack.pushNow(DefaultHandle) 方法
该方法直接将 DefaultHandle 放入 Stack 的内部数组中。
首先检查
item的recycleId和lastRecycledId是否都不为 0,如果不是则抛出IllegalStateException异常,表示该对象已经被回收过。设置
item的recycleId和lastRecycledId为OWN_THREAD_ID,标识该对象是由当前线程回收的。获取当前
Stack的大小size。如果
size达到maxCapacity(最大容量)或者dropHandle(item)方法返回true(表示应该丢弃该对象),则直接返回,丢弃该对象。如果
size等于内部数组elements的长度,则将elements扩容为当前大小的两倍,但不超过maxCapacity。将
item放入elements数组的size索引位置。将
Stack的size加 1。
6.3.2.Stack.pushLater(DefaultHandle, Thread) 方法
该方法将 DefaultHandle 放入与该 Stack 关联的 WeakOrderQueue 中。
从
DELAYED_RECYCLED这个ThreadLocal维护的Map<Stack<?>, WeakOrderQueue>中获取当前Stack对应的WeakOrderQueue。DELAYED_RECYCLED用于存储每个Stack关联的其他线程回收队列的头部。如果
queue为null,表示当前Stack还没有关联的WeakOrderQueue。检查
delayedRecycled的大小是否达到maxDelayedQueues(每个Stack允许关联的最大延迟队列数),如果达到则将一个伪造的WeakOrderQueue.DUMMY放入delayedRecycled中,并直接返回,丢弃该对象。尝试创建一个新的
WeakOrderQueue,通过WeakOrderQueue.allocate(this, thread)方法。如果分配失败(表示共享容量不足),则直接返回,丢弃该对象。将新创建的
WeakOrderQueue放入delayedRecycled中,与当前的Stack关联起来。
如果
queue是WeakOrderQueue.DUMMY,则直接返回,丢弃该对象。调用
queue.add(item)方法将DefaultHandle添加到该WeakOrderQueue中。
6.3.3.WeakOrderQueue.allocate(Stack<?>, Thread) 方法
该静态方法用于分配一个新的 WeakOrderQueue。
调用
reserveSpace(stack.availableSharedCapacity, LINK_CAPACITY)方法尝试预留LINK_CAPACITY大小的共享空间。availableSharedCapacity是Stack中可用的共享容量。如果预留空间成功,则创建一个新的
WeakOrderQueue实例并返回,否则返回null。
6.3.4.WeakOrderQueue 构造函数
WeakOrderQueue 的构造函数用于初始化一个新的延迟回收队列。
创建一个新的
Link实例作为WeakOrderQueue的head和tail。创建一个指向拥有该
Stack的线程的WeakReference,并赋值给owner字段。在
synchronized (stack)代码块中,将新创建的WeakOrderQueue设置为stack.head,并将原stack.head赋值给当前WeakOrderQueue的next字段。这样就将当前WeakOrderQueue插入到stack的WeakOrderQueue链表的头部。将当前
stack的availableSharedCapacity赋值给当前WeakOrderQueue的availableSharedCapacity。
6.4.Recycler.get() 方法
该方法用于从对象池中获取一个对象。
如果
maxCapacity为 0,则直接创建一个新的对象并返回(不进行池化)。从与当前线程关联的
ThreadLocal中获取对应的Stack实例。调用
stack.pop()方法尝试从Stack中弹出一个可用的DefaultHandle。如果
stack.pop()返回null,表示当前Stack中没有可用对象,则创建一个新的DefaultHandle(通过stack.newHandle())并使用newObject(handle)方法创建一个新的对象实例,并将该对象赋值给handle.value。返回
handle.value,即获取到的对象实例。
6.5.Stack.pop() 方法
该方法尝试从 Stack 的内部数组中弹出一个可用的 DefaultHandle。
获取当前
Stack的大小size。如果
size为 0,表示当前Stack为空,则调用scavenge()方法尝试从关联的WeakOrderQueue中转移一些对象到当前Stack。如果scavenge()返回false,表示没有成功转移到任何对象,则直接返回null。将
size减 1。从
elements数组的size索引位置获取一个DefaultHandle并赋值给ret。将
elements数组的size索引位置设置为null,释放引用。检查
ret的lastRecycledId是否不等于ret.recycleId,如果不是则抛出IllegalStateException异常,表示该对象被回收了多次。将
ret的recycleId和lastRecycledId重置为 0。更新
Stack的size。返回获取到的
DefaultHandle。
6.6.Stack.scavenge() 方法
该方法用于从关联的 WeakOrderQueue 中转移一些对象到当前的 Stack 中。
首先尝试继续上次未完成的清理操作,调用
scavengeSome()方法。如果成功转移了对象,则返回true。如果
scavengeSome()返回false,则重置清理游标prev为null,cursor为head(指向WeakOrderQueue链表的头部)。返回
false,表示本次scavenge()操作未能成功转移任何对象(可能是因为scavengeSome()第一次执行时也没有找到可转移的对象)。
6.7.Stack.scavengeSome() 方法
该方法具体执行从 WeakOrderQueue 中清理部分 DefaultHandle 到当前 Stack 的操作。
获取当前的清理游标
cursor。如果cursor为null,则将其指向head(WeakOrderQueue链表的头部)。如果head也为null,则直接返回false,表示没有需要清理的队列。设置一个
success标志为false。遍历
WeakOrderQueue链表,直到链表结束或者成功转移了对象。调用当前
WeakOrderQueue的transfer(this)方法,尝试将该队列的头部Link中的DefaultHandle转移到当前的Stack。如果转移成功,则设置success为true并跳出循环。获取当前
WeakOrderQueue的下一个队列next。如果当前
WeakOrderQueue的拥有者线程(owner)已经被回收(owner.get() == null),则尝试将该队列中剩余的所有数据都转移到当前Stack(通过循环调用transfer(this))。然后,如果prev不为null,则将prev的next指向当前WeakOrderQueue的next,从而将当前已失效的队列从链表中移除。如果当前
WeakOrderQueue的拥有者线程仍然存活,则将当前队列赋值给prev,以便在下次迭代中处理下一个队列。将
cursor指向下一个队列next。
更新
this.prev和this.cursor,保存当前的清理状态。返回
success,表示本次是否成功转移了对象。
6.8.WeakOrderQueue.transfer(Stack<?>) 方法
该方法将当前 WeakOrderQueue 的头部 Link 中的 DefaultHandle 数组迁移到指定的 Stack 中。
获取当前
WeakOrderQueue的头部Link。如果head为null,则返回false。如果
head的readIndex达到LINK_CAPACITY,说明该Link中的对象已经被完全转移,此时将head指向下一个Link。如果下一个Link为null,则返回false。获取头部
Link的起始读取索引srcStart和结束读取索引srcEnd(通过head.get()获取当前写入位置)。计算可以转移的对象数量
srcSize = srcEnd - srcStart。如果srcSize为 0,则返回false。获取目标
Stack的当前大小dstSize。计算预期迁移后的
Stack大小expectedCapacity = dstSize + srcSize。如果
expectedCapacity大于目标Stack的内部数组长度,则调用dst.increaseCapacity(expectedCapacity)尝试扩容。并将srcEnd调整为不超过扩容后的容量。如果
srcStart不等于srcEnd,则进行实际的对象迁移:获取头部
Link的对象数组srcElems和目标Stack的对象数组dstElems。遍历
srcElems从srcStart到srcEnd的元素:检查被迁移的
DefaultHandle的recycleId,确保没有被多次回收。将
srcElems的当前位置设置为null。调用目标
Stack的dropHandle(element)方法判断是否应该丢弃该对象。如果应该丢弃,则跳过。将该
DefaultHandle的stack属性设置为目标Stack。将该
DefaultHandle放入目标Stack的dstElems数组中,并更新newDstSize。
如果当前头部
Link的对象全部被转移 (srcEnd == LINK_CAPACITY) 并且存在下一个Link,则回收当前Link占用的共享空间,并将head指向下一个Link。更新头部
Link的readIndex为srcEnd。如果目标
Stack的大小没有发生变化 (dst.size == newDstSize),则返回false,表示没有成功转移任何对象。更新目标
Stack的大小dst.size为newDstSize。返回
true,表示成功转移了对象。
如果
srcStart等于srcEnd,表示当前头部Link中没有可转移的对象,则返回false。
7. 性能优化与最佳实践
7.1 性能优化策略
- 对象复用机制
通过DefaultHandle包装对象,实现对象的复用
使用Stack数组存储,避免链表带来的性能开销
采用ThreadLocal绑定,减少线程竞争
数据存储交给 JVM,对象生命周期管理交给 Recycler,各司其职提升整体性能
- 内存管理优化
默认容量32,按需扩容,避免内存浪费
WeakReference引用,防止内存泄漏
共享容量控制,限制跨线程对象传递
无论是堆内还是堆外 ByteBuf,用户均通过 buf.release() 释放资源,底层由 Recycler 自动处理差异
Recycler 结合 ResourceLeakDetector,能跟踪未正确释放的 ByteBuf,避免内存泄漏。池化对象在回收时会重置内部状态,确保下次使用时无残留数据
- 并发性能优化
线程本地缓存,减少锁竞争
延迟队列设计,平衡生产和消费
异步清理机制,提高响应性能
7.2 最佳实践
- 静态声明
private static final Recycler<MyObject> RECYCLER = new Recycler<MyObject>() {
@Override
protected MyObject newObject(Handle<MyObject> handle) {
return new MyObject(handle);
}
};
- 对象设计
public class MyObject {
private final Recycler.Handle<MyObject> handle;
private String data;
MyObject(Recycler.Handle<MyObject> handle) {
this.handle = handle;
}
public void recycle() {
// 清理对象状态
this.data = null;
// 回收对象
handle.recycle(this);
}
}
- 使用场景
对象创建成本高的场景
对象使用频繁的场景
需要严格控制GC的场景
7.3 注意事项
- 内存管理
及时回收对象,避免内存泄漏
合理设置maxCapacity,避免OOM
注意WeakOrderQueue的内存占用
- 线程安全
确保对象状态正确重置
避免重复回收同一对象
注意跨线程回收的性能影响
- 性能调优
监控对象池使用情况
根据场景调整容量参数
避免过度池化
8. 总结
Recycler通过精心的设计和优化,实现了高效的对象池化管理:
- 核心特性
线程本地缓存
跨线程对象传递
自动扩容和收缩
- 性能优势
减少对象创建开销
降低GC压力
提高内存利用率
- 应用建议
遵循最佳实践
合理使用对象池
注意性能监控
通过合理使用Recycler,可以显著提升应用程序的性能,特别是在高并发、低延迟场景下。但同时也需要注意对象池化带来的复杂性,在实际应用中需要权衡利弊。
