前言
最近在看netty源码,在阅读netty的过程中,深深的感到系统实现比较复杂,而源码不只是了解实现逻辑,还要从中挖掘出一些有价值的东西,学习到一些技术能够运用到实际的生产环境中。在探究netty高性能的原因途中,发现了一些代码觉得挺有用,所以记录下来以供参考。
正文
简介
由于java在运行过程中,即使有GC可以让我们无需关注内存释放,但频繁的创建和销毁对象也会导致GC性能下降,所以netty编写了轻量级的对象池来辅助管理。
Recycler原理
Recycler将对象进行循环使用,当一个对象使用完毕后,利用Recycler进行回收,当需要一个对象的时候将从Recycler取出一个对象,如果没有任何可以用的对象则new一个对象。
Recycler的三个主要方法如下:
column1 | column2 |
---|---|
T get() | 获取对象 |
newObject() | 需要自己实现的方法,创建一个新的对象 |
public final boolean recycle(T o, Handle handle) | 回收一个对象 |
/**
* 一个空的回收方法,表示不需要回收的对象,主要用于占位功能
* 当Stack的默认容量为0的时候使用 这时候无法使用回收功能
*/
@SuppressWarnings("rawtypes")
private static final Handle NOOP_HANDLE = new Handle() {
@Override
public void recycle(Object object) {
// NOOP
}
};
/**
* 当前线程ID和 WeakOrderQueueID
*/
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(Integer.MIN_VALUE);
private static final int OWN_THREAD_ID = ID_GENERATOR.getAndIncrement();
/**
* Stack默认的最大初始最大容量:4kb
*/
private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024;
/**
* Stack的默认容量
* io.netty.recycler.maxCapacityPerThread和io.netty.recycler.maxCapacity参数配置
* 当参数小于0的时候设置默认上面4kb,等于0的时候表示禁用
*/
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;
/**
* Stack默认的初始容量,下面代表表示默认为256k
* 自动根据需要进行扩容,直到达到MAX_CAPACITY_PER_THREAD
*/
private static final int INITIAL_CAPACITY;
/**
* 最大共享容量因子:maxCapacity / maxSharedCapacityFactor,默认为2
*/
private static final int MAX_SHARED_CAPACITY_FACTOR;
/**
* 每个线程可拥有WeakOrderQueue数量
* 通过io.netty.recycler.maxDelayedQueuesPerThread数量
* 默认为2*cpu核数
*/
private static final int MAX_DELAYED_QUEUES_PER_THREAD;
/**
* 每个WeakOrderQueue内部Link中Handle的数量
* io.netty.recycler.linkCapacity配置 默认为16
* Link组成一个Link链表
*/
private static final int LINK_CAPACITY;
/**
* 回收因子
* io.netty.recycler.ratio配置 默认为8
*/
private static final int RATIO;
private static final int DELAYED_QUEUE_RATIO;
以上是Recycler的静态参数列表,比较容易理解
从以上参数中可以看出来,Recycler内部有三个主要参数:
- Stack:不是JAVA内部的Stack,但是功能差不多,用于存储当前线程回收的对象
- WeakOrderQueue:用于存储其他线程回收到当前线程的对象
- Link:WeakOrderQueue存储对象的元素,一个Link默认存储16个对象,超过了创建一个新的Link
- DefaultHandle:默认的handler实现
/**
* 每个线程包含一个Stack 存储在ThreadLocal中
* 根据Recycler的构造函数初始化Stack
*/
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
@Override
protected Stack<T> initialValue() {
return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
interval, maxDelayedQueuesPerThread, delayedQueueInterval);
}
@Override
protected void onRemoval(Stack<T> value) {
// Let us remove the WeakOrderQueue from the WeakHashMap directly if its safe to remove some overhead
if (value.threadRef.get() == Thread.currentThread()) {
if (DELAYED_RECYCLED.isSet()) {
DELAYED_RECYCLED.get().remove(value);
}
}
}
};
private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED =
new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {
@Override
protected Map<Stack<?>, WeakOrderQueue> initialValue() {
return new WeakHashMap<Stack<?>, WeakOrderQueue>();
}
};
都是放到封装过的ThreadLocal里面
Recyclern内部图
Recycler使用了Stack来存储同一个类型的Handle,这个图表达了上面代码的作用用于保存线程共享对象,当非当前线程去回收一个对象的时候,就会使用到共享对象。
DefaultHandle实现
首先看一下DefaultHandle内部实现
private static final class DefaultHandle<T> implements Handle<T> {
int lastRecycledId;
int recycleId;
boolean hasBeenRecycled;
Stack<?> stack;
Object value;
DefaultHandle(Stack<?> stack) {
this.stack = stack;
}
@Override
public void recycle(Object object) {
if (object != value) {
throw new IllegalArgumentException("object does not belong to handle");
}
Stack<?> stack = this.stack;
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
}
stack.push(this);
}
}
DefaultHandle将需要回收的对象给包装,使用lastRecycledId和recycleId来组织重复回收,做的操作仅仅是放入stack
Stack内部实现
DefaultHandle实现很简单,把自己放入了Stack,现在看一下Stack内部有哪些元素
/**
* 保存了Stack所属的线程
* 按照对象回收策略 如果对象没有被强引用了就可以被回收了 这里使用弱引用是因为
* 如果用户持有Handle对象一直不释放 Stack又持有Handle的强引用 使用弱引用可以让JVM释放该线程对象
*/
final WeakReference<Thread> threadRef;
/**
* 可共享内存大小 按照之前配置的共享因子计算 默认2kb
* 表示其他线程可回收当前线程的最大容量,这也表示当前线程可以被回收最大容量为(4+2)kb
*/
final AtomicInteger availableSharedCapacity;
private final int maxDelayedQueues;
private final int maxCapacity;
private final int interval;
private final int delayedQueueInterval;
DefaultHandle<?>[] elements;
int size;
private int handleRecycleCount;
private WeakOrderQueue cursor, prev;
/**
* 线程B回收线程A创建的对象时,线程B会为线程A的Stack对象创建一个WeakOrderQueue对象,
* 这个WeakOrderQueue指向head,用于后续线程A对对象的查找操作
**/
private volatile WeakOrderQueue head;
使用了elements数组来存储Handle
DefaultHandle<T> pop() {
int size = this.size;
if (size == 0) {
if (!scavenge()) {
return null;
}
//
size = this.size;
if (size <= 0) {
// double check, avoid races
return null;
}
}
size --;
DefaultHandle ret = elements[size];
elements[size] = null;
this.size = size;
if (ret.lastRecycledId != ret.recycleId) {
throw new IllegalStateException("recycled multiple times");
}
ret.recycleId = 0;
ret.lastRecycledId = 0;
return ret;
}
取出一个对象,注意第一个if后面重新给size赋值了一次,因为在此过程中调用了scavenge,可能有其他线程handle传递,所以要保证size是一致的。其余的过程就是对数组的操作
当前线程对象池没有对象的时候,可以从其他线程的保存的对象中拿出来,核心是调用了WeakOrderQueue的transfer方法
void push(DefaultHandle<?> item) {
Thread currentThread = Thread.currentThread();
if (threadRef.get() == currentThread) {
pushNow(item);
} else {
pushLater(item, currentThread);
}
}
private void pushNow(DefaultHandle<?> item) {
if ((item.recycleId | item.lastRecycledId) != 0) {
throw new IllegalStateException("recycled already");
}
item.recycleId = item.lastRecycledId = OWN_THREAD_ID;
int size = this.size;
if (size >= maxCapacity || dropHandle(item)) {
// Hit the maximum capacity or should drop - drop the possibly youngest object.
return;
}
//扩容
if (size == elements.length) {
elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
}
elements[size] = item;
this.size = size + 1;
}
如果是当前线程,调用pushNow方法,将对象放入数组中,需要注意容量来选择扩容。
/**
* 从map中取出queue或者新建一个,前提是数量合法
*/
private void pushLater(DefaultHandle<?> item, Thread thread) {
//不支持跨线程回收
if (maxDelayedQueues == 0) {
return;
}
Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
WeakOrderQueue queue = delayedRecycled.get(this);
if (queue == null) {
if (delayedRecycled.size() >= maxDelayedQueues) {
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
// Check if we already reached the maximum number of delayed queues and if we can allocate at all.
if ((queue = newWeakOrderQueue(thread)) == null) {
return;
}
delayedRecycled.put(this, queue);
} else if (queue == WeakOrderQueue.DUMMY) {
return;
}
queue.add(item);
}
如果不是当先线程,就要去map里面找到属于该线程的queue放入,需要判断是否超过最大值
每当一个线程回收对象的时候,就会成为Stack内部的head,为的是将其他线程的Queue与Stack关联。
private WeakOrderQueue cursor, prev;
而cursor代表Queue形成的练表中,当前寻找的Queue,prev表示cursor的上一个
Stack介绍完了,底层维护了一个Handle类型的数组,如果是当前线程就操作这个数组,操作方式和ArrayList类似,同时还支持去其他线程的queue里面拿对象
WeakOrderQueue实现
WeakOrderQueue继承了WeakReference,具体JAVA三种引用可以百度查询。
List元素内部也是包含Handle数组,并且自身是AtomicInteger就可以用来计数。
在添加新queue的时候,如果超出了最大队列数量,就放入这个DUMMY 不做任何操作。
Head是Queue的头节点,reclaimAllSpaceAndUnlink表示回收所有空间并且取消链接,因为防止GC的裙带关系
创建一个新的Link,需要检测空间是否足够,不然就返回null
看注释,构造时候将tail赋值给了head.link
add方法,如果一个link内部数组满了,就需要创建下一个link来链接,需要判断有没有足够的空间来创建,没有就直接丢弃这个handle
boolean transfer(Stack<?> dst) {
//如果head为空表示这个queue没有handle可以用
Link head = this.head.link;
if (head == null) {
return false;
}
//表示这个link已经读完了 需要读下一个link 没有就返回false
if (head.readIndex == LINK_CAPACITY) {
if (head.next == null) {
return false;
}
head = head.next;
this.head.relink(head);
}
//检查当先可读索引和当前容量 相等表示无法读了
final int srcStart = head.readIndex;
int srcEnd = head.get();
final int srcSize = srcEnd - srcStart;
if (srcSize == 0) {
return false;
}
final int dstSize = dst.size;
final int expectedCapacity = dstSize + srcSize;
if (expectedCapacity > dst.elements.length) {
final int actualCapacity = dst.increaseCapacity(expectedCapacity);
srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);
}
//重新计算了srcEnd 需要再判断
if (srcStart != srcEnd) {
final DefaultHandle[] srcElems = head.elements;
final DefaultHandle[] dstElems = dst.elements;
//stack的索引 从这个索引开始跟新
int newDstSize = dstSize;
for (int i = srcStart; i < srcEnd; i++) {
DefaultHandle<?> element = srcElems[i];
if (element.recycleId == 0) {
element.recycleId = element.lastRecycledId;
} else if (element.recycleId != element.lastRecycledId) {
throw new IllegalStateException("recycled already");
}
//删除link的元素 将这个元素放入Stack中
srcElems[i] = null;
if (dst.dropHandle(element)) {
continue;
}
element.stack = dst;
dstElems[newDstSize ++] = element;
}
if (srcEnd == LINK_CAPACITY && head.next != null) {
// Add capacity back as the Link is GCed.
this.head.relink(head.next);
}
head.readIndex = srcEnd;
if (dst.size == newDstSize) {
return false;
}
dst.size = newDstSize;
return true;
} else {
return false;
}
}
}
transfer方法的参数是Stack,表示要将这个queue中的对象转换到Stack里面,前面一段注释是对一些条件的判断,比如link是否为空或者是否还有值可读。当一切准备就绪的时候,对数组进行拷贝,将queue内部的对象复制到Stack并且删除
Netty 实现了轻量的对象池,使用了threadLocal,避免了多线程下取数据时可能出现的线程安全问题。同时,为了实现多线程回收同一个实例,让每个线程对应一个队列,队列链接在 Stack 对象上形成链表,使用++软引用map++和++软引用线程++也避免了内存泄漏,解决了多线程回收时的安全问题