在netty中Recycler用来实现对象池,以达到对象的循环利用,它是netty实现的一个轻量级对象回收站,具体的实现有:堆内存对应PooledHeapByteBuf,而直接内存对应的是PooledDirectByteBuf。本文基于源码针对netty-4.1.27。
对象池产生的背景
创建大量对象实例的消耗不小,在之前初探对象的内存布局就讲过,若能通过复用,能够避免频繁创建新对象和销毁对象(GC)带来的损耗。
我们知道jvm比cpp就多了一个GC,若能很好的控制GC回收频率,性能上可以达到cpp的同一个量级。
Recycler结构
Recycler主要的方法:
- get():获取一个对象
- recycle(T, Handle):回收一个对象
- newObject(Handle):当没有可用对象时创建对象的实现方法
DefaultHandle |
Recycler中缓存的对象都会包装成DefaultHandle类 |
Stack |
存储当前线程回收的对象。Stack会与线程绑定,即每个用到Recycler的线程都会拥有1个Stack,在该线程中获取对象都是在该线程的Stack中pop出一个可用对象。 对象的获取和回收对应Stack的pop和push,即获取对象时从Stack中pop出1个DefaultHandle,回收对象时将对象包装成DefaultHandle push到Stack中。 |
WeakOrderQueue |
存储其它线程回收到当前线程stack的对象,每个线程的Stack拥有1个WeakOrderQueue链表,链表每个节点对应1个其它线程的WeakOrderQueue,其它线程回收到该Stack的对象就存储在这个WeakOrderQueue里。 当某个线程从Stack中获取不到对象时会从WeakOrderQueue中获取对象。 |
Link |
WeakOrderQueue中包含1个Link链表,回收对象存储在链表某个Link节点里,当Link节点存储的回收对象满了时会新建1个Link放在Link链表尾。 |
源码破析
//和PooledDirectByteBuf的源码同理,都是池化具体实现
class PooledHeapByteBuf extends PooledByteBuf {
private static final Recycler RECYCLER = new Recycler() {
protected PooledHeapByteBuf newObject(Handle handle) {
return new PooledHeapByteBuf(handle, 0);
}
};
static PooledHeapByteBuf newInstance(int maxCapacity) {
PooledHeapByteBuf buf = (PooledHeapByteBuf)RECYCLER.get();
buf.reuse(maxCapacity);
return buf;
}
}
//也是池化具体实现
public final class ChannelOutboundBuffer {
static final class Entry {
private static final Recycler RECYCLER = new Recycler() {
protected ChannelOutboundBuffer.Entry newObject(Handle handle) {
return new ChannelOutboundBuffer.Entry(handle);
}
};
}
}
//对象池的抽象类
public abstract class Recycler {
/**
* 表示一个不需要回收的包装对象,用于在禁止使用Recycler功能时进行占位的功能
* 仅当io.netty.recycler.maxCapacityPerThread<=0时用到
*/
private static final Recycler.Handle NOOP_HANDLE = new Recycler.Handle() {
public void recycle(Object object) {
}
};
//1.当前线程ID,WeakOrderQueue的id
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(-2147483648);
private static final int OWN_THREAD_ID;
private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4096;
/**
* 每个Stack默认的最大容量
* 注意:
* 1、当io.netty.recycler.maxCapacityPerThread<=0时,禁用回收功能(在netty中,只有=0可以禁用,<0默认使用4k)
* 2、Recycler中有且只有两个地方存储DefaultHandle对象(Stack和Link),
* 最多可存储MAX_CAPACITY_PER_THREAD + 最大可共享容量 = 4k + 4k/2 = 6k
*
* 实际上,在netty中,Recycler提供了两种设置属性的方式
* 第一种:-Dio.netty.recycler.ratio等jvm启动参数方式
* 第二种:Recycler(int maxCapacityPerThread)构造器传入方式
*/
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;
//每个Stack默认的初始容量,默认为256,后续根据需要进行扩容,直到<=MAX_CAPACITY_PER_THREAD
private static final int INITIAL_CAPACITY;
//最大可共享的容量因子= maxCapacity / maxSharedCapacityFactor,默认为2
private static final int MAX_SHARED_CAPACITY_FACTOR;
//每个线程可拥有多少个WeakOrderQueue,默认为2*cpu核数,实际上就是当前线程的Map, WeakOrderQueue>的size最大值
private static final int MAX_DELAYED_QUEUES_PER_THREAD;
/**
* WeakOrderQueue中的Link中的数组DefaultHandle>[] elements容量,默认为16,
* 当一个Link中的DefaultHandle元素达到16个时,会新创建一个Link进行存储,这些Link组成链表,当然
* 所有的Link加起来的容量要<=最大可共享容量。
*/
private static final int LINK_CAPACITY;
//回收因子,默认为8,即默认每8个对象,允许回收一次,直接扔掉7个,可以让recycler的容量缓慢的增大,避免爆发式的请求
private static final int RATIO;
private final int maxCapacityPerThread;
private final int maxSharedCapacityFactor;
private final int ratioMask;
private final int maxDelayedQueuesPerThread;
/**
* 每一个线程包含一个Stack对象
* 1、每个Recycler对象都有一个threadLocal
* 原因:因为一个Stack要指明存储的对象泛型T,而不同的Recycler对象的T可能不同,所以此处的FastThreadLocal是对象级别
* 2、每条线程都有一个Stack对象
*/
private final FastThreadLocal> threadLocal;
/**
* 每一个线程对象包含一个,为其他线程创建的WeakOrderQueue对象
* 1、每个Recycler类(而不是每一个Recycler对象)都有一个DELAYED_RECYCLED
* 原因:可以根据一个Stack对象唯一的找到一个WeakOrderQueue对象,所以此处不需要每个对象建立一个DELAYED_RECYCLED
* 2、由于DELAYED_RECYCLED是一个类变量,所以需要包容多个T,此处泛型需要使用?
* 3、WeakHashMap:当Stack没有强引用可达时,整个Entry{Stack>, WeakOrderQueue}都会加入相应的弱引用队列等待回收
*/
private static final FastThreadLocal
使用示例
创建一个Recycler对象池,重写了其newObject(Handle handle)
,当对象池中没有数据时,就调用该方法新建对象,而传入Recycler.Handle
用于该对象的回收操作。
public class RecycleTest {
private static final Recycler RECYCLER = new Recycler() {
@Override
protected User newObject(Handle handle) {
return new User(handle);
}
};
private static class User {
private final Recycler.Handle handle;
public User(Recycler.Handle handle) {
this.handle = handle;
}
public void recycle() {
//回收
handle.recycle(this);
}
}
@Test
public void recycle() {
User user = RECYCLER.get();
//回收
user.recycle();
User user1 = RECYCLER.get();
//返回true
System.out.println(user1 == user);
}
}
Netty 实现了轻量的对象池,通过使用类 threadLocal,避免了多线程下取数据时可能出现的线程安全问题。同时,为了实现多线程回收同一个实例,让每个线程对应一个队列,队列链接在 Stack 对象上形成链表,还使用软引用的map 和 软引用的 thradl 也避免了内存泄漏,至此解决了多线程回收时的安全问题。
引用资料