众所周知Netty是一个高性能的NIO通信框架, Netty开发者都是很厉害的, 不仅框架设计很精巧,针对高性能做了很多优化, 很多细节方面都处理的很好. 做中间件用C语言开发当然是速度最快的, 用Java做NIO通信中间件, 需要对java语言有很深的认识, 做到扬长弊短.
比如Java的面向对象编程, 提高了开发的效率, 但是面向对象编程的附属品垃圾回收机制, 在高并发场景下还是一个瓶颈, 特别是Netty的I/O通信采用的是非堆内存, 对这种内存对象垃圾回收效率会比JVM的堆内存效率低, 频繁的GC对虚拟机性能有很大影响. Netty中打交道最多个就是各种Buffer, 通信I/O都是在处理Buffer对象. 因为Buf对象实在使用太频繁了, 哪怕提升一点点, 对性能也是有不少提升的.
下面我们就来看下Netty在这方面做了哪些优化. io.netty.util.Recycler 类是Netty自定义的一个对象池, 借助于这个类可以从对象池中获取对象, 使用完了回收到对象池中, 减少GC.我现在看的版本是Netty的4.1.13.Final版, 差不多是最新的版本了. Netty的开发社区还是很活跃的, 很多代码都在持续优化中, 有bug也都是能及时修复的.比如Recycler这个类, 也是一直在持续优化中, 比老版本的类功能更强大.
这是一个抽象类, 子类继承它时需要实现 newObject(Handle handle) 方法, 因为不同的子类针对不同的对象进行池化, 具体是什么对象由子类自己实现.
内部是用一个对象数组维护缓存的对象.
1. io.netty.util.Recycler#get方法 : 是获取对象池中对象的入口, 如果有可用对象,直接返回, 没有就调用上面提到的newObject方法创建一个.
2. io.netty.util.Recycler#recycle方法: 是对象使用完毕后回收对象的, 现在已经标记@Deprecated不建议使用了, 建议使用io.netty.util.Recycler.Handle方法. 可以看下4.0.x版本中都是用的这个方法, 这个方法只能回收当前线程创建的对象,负责直接返回false不回收,交给GC处理了. 方法io.netty.util.Recycler.DefaultHandle#recycle优势就是, 可以回收不是当前线程创建的对象, 复用性和性能更好了.
这是Recycler中定义的一个类, 主要也只有Recycler会访问到, Stack就是维护对象池的数据结构了.上面提到的io.netty.util.Recycler#get方法是从FastThreadLocal从获取池中对象,所以这个对象池是各个线程独立的,也就是每个线程都对应一个Stack.
FastThreadLocal是Netty自己定义的一个类似于JDK的ThreadLocal但是性能更高的一个数据结构,ThreadLocal内部访问数据是Map型的数据访问,FastThreadLocal内部是数组,直接通过索引访问会更快.
这个Recycler私有的一个类,用来暂存待回收的对象.
上面提到了io.netty.util.Recycler.DefaultHandle#recycle方法,可以回收不是当前线程创建的对象. 主要就是靠这个类实现的.
recycle方法判断,如果是当前线程创建的对象, 直接就把对象放到当前线程对应的Stack中. 如果不是, 则放入WeakOrderQueue中, 当然WeakOrderQueue会和回收的对象所对应的Stack是相关联的. 在从Stack获取对象池时,如果对象池为空,会尝试从对应的WeakOrderQueue中恢复对象,这样就实现了回收的功能. WeakOrderQueue可以有多个, 多个WeakOrderQueue是一个链表结构, 可以依次访问.
还有个东西要注意下,由于Stack是每个线程的本地变量,所以Stack里的所有方法都是线程安全的. 具体还用到了各种线程安全问题, 还有WeakReference弱引用的使用, 以及位运算控制内存池缓慢增长 等各种细节方面都考虑的很好, 确实很厉害. 要做到对这个类有充分的理解, 需要熟悉java的GC机制, 这块我后面还要再研究一下.
/**
* Light-weight object pool based on a thread-local stack. // 一个轻量的对象池
*
* @param the type of the pooled object
*/
public abstract class Recycler {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Recycler.class);
@SuppressWarnings("rawtypes")
private static final Handle NOOP_HANDLE = new Handle() { // 表示一个不需要回收的包装对象
@Override
public void recycle(Object object) { // 用于maxCapacityPerThread == 0时,关闭对象回收功能.
// NOOP
}
};
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(Integer.MIN_VALUE); // 线程安全的自增计数器,用来做唯一标记的.
private static final int OWN_THREAD_ID = ID_GENERATOR.getAndIncrement(); //static变量, 生成并获取一个唯一id, 标记当前的线程.
private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 32768; // Use 32k instances as default.
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD; // 每个线程的Stack最多缓存多少个对象
private static final int INITIAL_CAPACITY; // 初始化容量
private static final int MAX_SHARED_CAPACITY_FACTOR; // 最大可共享的容量
private static final int MAX_DELAYED_QUEUES_PER_THREAD; // WeakOrderQueue最大数量
private static final int LINK_CAPACITY; // WeakOrderQueue中的数组DefaultHandle>[] elements容量
private static final int RATIO; // 掩码
static { // 这里做到了各种参数都是可配置的, 可以根据实际的压测情况, 调节对象池的参数
// In the future, we might have different maxCapacity for different object types.
// e.g. io.netty.recycler.maxCapacity.writeTask
// io.netty.recycler.maxCapacity.outboundBuffer
int maxCapacityPerThread = SystemPropertyUtil.getInt("io.netty.recycler.maxCapacityPerThread",
SystemPropertyUtil.getInt("io.netty.recycler.maxCapacity", DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD));
if (maxCapacityPerThread < 0) {
maxCapacityPerThread = DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD;
}
DEFAULT_MAX_CAPACITY_PER_THREAD = maxCapacityPerThread;
MAX_SHARED_CAPACITY_FACTOR = max(2,
SystemPropertyUtil.getInt("io.netty.recycler.maxSharedCapacityFactor",
2));
MAX_DELAYED_QUEUES_PER_THREAD = max(0,
SystemPropertyUtil.getInt("io.netty.recycler.maxDelayedQueuesPerThread",
// We use the same value as default EventLoop number
NettyRuntime.availableProcessors() * 2));
LINK_CAPACITY = safeFindNextPositivePowerOfTwo(
max(SystemPropertyUtil.getInt("io.netty.recycler.linkCapacity", 16), 16));
// By default we allow one push to a Recycler for each 8th try on handles that were never recycled before. // 默认每过8次,允许回收一次
// This should help to slowly increase the capacity of the recycler while not be too sensitive to allocation // 可以让recycler的容量缓慢的增大,避免爆发式的请求吧.
// bursts.
RATIO = safeFindNextPositivePowerOfTwo(SystemPropertyUtil.getInt("io.netty.recycler.ratio", 8)); // 值默认为8, 也就是二进制 1000
if (logger.isDebugEnabled()) {
if (DEFAULT_MAX_CAPACITY_PER_THREAD == 0) {
logger.debug("-Dio.netty.recycler.maxCapacityPerThread: disabled");
logger.debug("-Dio.netty.recycler.maxSharedCapacityFactor: disabled");
logger.debug("-Dio.netty.recycler.linkCapacity: disabled");
logger.debug("-Dio.netty.recycler.ratio: disabled");
} else {
logger.debug("-Dio.netty.recycler.maxCapacityPerThread: {}", DEFAULT_MAX_CAPACITY_PER_THREAD);
logger.debug("-Dio.netty.recycler.maxSharedCapacityFactor: {}", MAX_SHARED_CAPACITY_FACTOR);
logger.debug("-Dio.netty.recycler.linkCapacity: {}", LINK_CAPACITY);
logger.debug("-Dio.netty.recycler.ratio: {}", RATIO);
}
}
INITIAL_CAPACITY = min(DEFAULT_MAX_CAPACITY_PER_THREAD, 256);
}
private final int maxCapacityPerThread;
private final int maxSharedCapacityFactor;
private final int ratioMask;
private final int maxDelayedQueuesPerThread;
private final FastThreadLocal> threadLocal = new FastThreadLocal>() { //FastThreadLocal是线程本地变量, 所以每个线程都对应一个自己的Stack.
@Override
protected Stack initialValue() {
return new Stack(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
ratioMask, maxDelayedQueuesPerThread);
}
};
protected Recycler() {
this(DEFAULT_MAX_CAPACITY_PER_THREAD);
}
protected Recycler(int maxCapacityPerThread) {
this(maxCapacityPerThread, MAX_SHARED_CAPACITY_FACTOR);
}
protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor) {
this(maxCapacityPerThread, maxSharedCapacityFactor, RATIO, MAX_DELAYED_QUEUES_PER_THREAD);
}
protected Recycler(int maxCapacityPerThread, int maxSharedCapacityFactor,
int ratio, int maxDelayedQueuesPerThread) {
ratioMask = safeFindNextPositivePowerOfTwo(ratio) - 1; //根据ratio获取一个掩码,默认为8,那么ratioMask二进制就是 "111"
if (maxCapacityPerThread <= 0) {
this.maxCapacityPerThread = 0;
this.maxSharedCapacityFactor = 1;
this.maxDelayedQueuesPerThread = 0;
} else {
this.maxCapacityPerThread = maxCapacityPerThread;
this.maxSharedCapacityFactor = max(1, maxSharedCapacityFactor);
this.maxDelayedQueuesPerThread = max(0, maxDelayedQueuesPerThread);
}
}
@SuppressWarnings("unchecked")
public final T get() {
if (maxCapacityPerThread == 0) { // 通过修改maxCapacityPerThread=0可以关闭回收功能, 默认值是32768
return newObject((Handle) NOOP_HANDLE);
}
Stack stack = threadLocal.get(); // 获取当前线程对应的Stack
DefaultHandle handle = stack.pop(); // 从对象池获取对象
if (handle == null) {
handle = stack.newHandle();
handle.value = newObject(handle); // 没有对象,则调用子类的newObject方法创建新的对象
}
return (T) handle.value;
}
/**
* @deprecated use {@link Handle#recycle(Object)}. //旧的方法
*/
@Deprecated
public final boolean recycle(T o, Handle handle) {
if (handle == NOOP_HANDLE) {
return false;
}
DefaultHandle h = (DefaultHandle) handle;
if (h.stack.parent != this) { // 旧的方法,如果不是当前线程的, 直接不回收了.
return false;
}
h.recycle(o);
return true;
}
final int threadLocalCapacity() {
return threadLocal.get().elements.length;
}
final int threadLocalSize() {
return threadLocal.get().size;
}
protected abstract T newObject(Handle handle);
public interface Handle {
void recycle(T object);
}
static final class DefaultHandle implements Handle { // DefaultHandle就是就是Stack的包装对象,持有stack的引用,可以回收自己到stack中;
private int lastRecycledId; //标记最新一次回收的线程id
private int recycleId; //也是一个标记,是用来回收前的校验的.
boolean hasBeenRecycled; //标记是否已经被回收
private Stack> stack; //持有stack的引用
private 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.push(this); //可以回收自己到stack中
}
}
private static final FastThreadLocal
dropHandle方法里有用到一个掩码ratioMask, 这个必须是2的次方-1,
如果按默认值7 (“111”) , 只要低三位不全0, 就会返回true本次忽略不做回收. 所以返回true概率是 7/8, 目的是为了让回收动作缓慢一些, 内存池慢慢的增加, 减少对系统的压力. 不得不说作者考虑的真仔细.
1
10
11
100
101
110
111
1000 <--
1001
1010
1011
1100
1101
1110
1111
10000 <--
package comm;
import com.github.ltsopensource.core.support.SystemClock;
import io.netty.util.Recycler;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class RecyclerTest {
static class WrapRecycler {
private List list;
private final static Recycler RECYCLER = new Recycler() {
@Override
protected WrapRecycler newObject(Handle handle) {
return new WrapRecycler(handle);
}
};
Recycler.Handle handle;
WrapRecycler(Recycler.Handle handle) {
this.handle = handle;
this.list = new ArrayList<>(1000);
}
List getList() {
return list;
}
static WrapRecycler getInstance() {
return RECYCLER.get();
}
void recycle() {
handle.recycle(this);
}
}
@Test
public void testDifferentThreadRecycle() throws InterruptedException, IOException {
System.out.println("Main thread started ...");
final WrapRecycler instance = WrapRecycler.getInstance();
instance.getList().add("111"); // main 线程放入一个字符串
final CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(new Runnable() { // 这里新创建一个线程,在新的线程中可以回收main线程中的对象.
@Override
public void run() {
System.out.println("Sub thread started ...");
List list = instance.getList();
list.add("222"); // 子线程放入一个字符串.
instance.recycle(); // 对main线程从对象池中回去的对象家进行回收动作.
System.out.println("Sub Thread get list : " + WrapRecycler.getInstance().getList()); // 在子线程中从对象池获取对象
countDownLatch.countDown();
}
}).start();
countDownLatch.await();
System.out.println("Main Thread get list : " + WrapRecycler.getInstance().getList()); // 在主线程中从对象池获取对象
System.in.read();
}
}
可以看到执行结果, 子线程可以回收主线程从主线程对象池中的对象, 但是只是回收到主线程对应的对象池里了, 各个线程对应的对象池时独立的.
Main thread started ...
Sub thread started ...
Sub Thread get list : []
Main Thread get list : [111, 222]
进而可以写两个demo,比较下使用对象池和依赖JVM垃圾回收的执行效率.
我本地测试下来, 发现同样都是在堆内存下, 如果创建的都是很小的对象, 使用netty的这个对象池没有什么优势, 但是如果创建的是比较复杂比较大的对象, 使用对象池执行时间只有前者的七分之一. 理论上, 对象垃圾回收的成本越高, 这个对象池的效果就会越明显的, 特别是在Netty的非堆内存场景下, 垃圾回收的成本会更高.
大家如果在代码里要使用的话, 可以针对自己的场景进行测试下.