此接口来进行实际的内存分配,默认使用的是ByteBufAllocator.DEFAULT,初始化时会根据配置和平台进行赋值。
io.netty.allocator.type可以设置为unpooled和pooled指定是否需要缓冲池,如果不设置则会根据平台判断。逻辑如下:
static {
String allocType = SystemPropertyUtil.get(
"io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
allocType = allocType.toLowerCase(Locale.US).trim();
ByteBufAllocator alloc;
if ("unpooled".equals(allocType)) {
alloc = UnpooledByteBufAllocator.DEFAULT;
} else if ("pooled".equals(allocType)) {
alloc = PooledByteBufAllocator.DEFAULT;
} else {
alloc = PooledByteBufAllocator.DEFAULT;
}
DEFAULT_ALLOCATOR = alloc;
}
AbstractByteBufAllocator继承此接口,实现了一些通用的逻辑,比如最基本的buffer方法。
@Override
public ByteBuf buffer() {
if (directByDefault) {
return directBuffer();
}
return heapBuffer();
}
根据directByDefault属性来判断创建不同类型的buffer,至于directByDefault属性的赋值,在此抽象类中并没有赋值操作。
经过一系列的重载之后调用了如下两个抽象方法:
protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity);
protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity);
它们的实现在UnPooledByteBufAllocator或者PooledByteBufAllocator中,我们先看前者的实现逻辑。
UnPooledByteBufAllocator
该分配器分配的Bytebuf底层为不使用对象池技术字节数组。至于直接内存还是堆内存取决于参数,至于是否通过Unsafe分配取决于配置以及平台,这里多说一点,一般创建对象通过new关键字,即三条指令创建对象,反射也一样。其实Unsafe/safe分配器对于ByteBuf的创建没有多大的差异,只是写入数据的效率Unsafe是通过内存地址直接写入,会更快一些。
从它的构造函数入手:
public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector, boolean tryNoCleaner) {
super(preferDirect);
this.disableLeakDetector = disableLeakDetector;
noCleaner = tryNoCleaner && PlatformDependent.hasUnsafe()
&& PlatformDependent.hasDirectBufferNoCleanerConstructor();
}
第一个参数表示是否为直接内存,第二个为是否开启内存泄漏检测,第三个参数由平台决定的,表明Netty是否参与直接内存的回收。
其中还调用了父类的构造方法:
protected AbstractByteBufAllocator(boolean preferDirect) {
directByDefault = preferDirect && PlatformDependent.hasUnsafe();
emptyBuf = new EmptyByteBuf(this);
}
主要是对前面刚提到的directByDefault属性进行赋值。UnPooledByteBufAllocator对newDirectBuffer和newHeapBuffer都进行了实现。
PooledByteBufAllocator
Netty实际使用内存分配器会根据配置采用PooledByteBufAllocator.DEFAULT,所有事件循环线程使用的是一个分配器实例。PooledByteBufAllocator将内存分为PoolArena,PoolChunk和PoolPage,Chunk中包含多个内存页,Arena包含3个Chunk。
先从重要的常量看起。
// 默认堆内存类型PoolArena个数
private static final int DEFAULT_NUM_HEAP_ARENA;
// 默认直接内存类型PoolArena个数
private static final int DEFAULT_NUM_DIRECT_ARENA;
// 默认页大小
private static final int DEFAULT_PAGE_SIZE;
// 每个chunk中的page是用平衡二叉树映射管理每个PoolSubpage是否被分配
// maxOrder为树的深度,深度为maxOrder层的节点数量为1 << maxOrder,maxOrder =< 11
private static final int DEFAULT_MAX_ORDER;
//默认的tiny cache 的大小 512
private static final int DEFAULT_TINY_CACHE_SIZE;
//默认的small cache的大小 256
private static final int DEFAULT_SMALL_CACHE_SIZE;
//默认的normal cache的大小 64
private static final int DEFAULT_NORMAL_CACHE_SIZE;
private static final int MIN_PAGE_SIZE = 4096;
//最大Chunk的大小,默认等于2的30次方 即1G
private static final int MAX_CHUNK_SIZE = (int) (((long) Integer.MAX_VALUE + 1) / 2);
在静态代码块中对这些常量进行初始化操作。PooledByteBufAllocator内部有两个重要数组HeapArena和DirectArena,用来记录堆内存和直接内存当前的使用状态。PoolArena都实现了PoolArenaMetric接口,用于测量内存使用状况。数组的长度即为上面的定义的常量。除此之外,还有一个重要的对象PoolThreadLocalCache,其继承了FastThreadLocal,用于线程的本地缓存,在内存管理中,线程本地内存缓区的信息会保存在PoolThreadCache对象中。PooledByteBufAllocator覆盖的newHeapBuffer和newDirectBuffer用来分配内存。构造函数中对成员变量进行过初始化操作。
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
int tinyCacheSize, int smallCacheSize, int normalCacheSize,
boolean useCacheForAllThreads, int directMemoryCacheAlignment) {
super(preferDirect);
// PoolThreadLocalCache继承自FastThreadLocal,用来保存PoolThreadCache
threadCache = new PoolThreadLocalCache(useCacheForAllThreads);
this.tinyCacheSize = tinyCacheSize;
this.smallCacheSize = smallCacheSize;
this.normalCacheSize = normalCacheSize;
// chunkSize = DEFAULT_PAGE_SIZE(8KB) << DEFAULT_MAX_ORDER(11)
chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);
// 检查pageSize是否大于4K且为2的幂次方,如果不是则抛异常,返回的值为pageSize二进制的尾部0的个数
// pageSize如果为是8192时它的二进制表示是10000000000000,那么这个pageShifts就是13
int pageShifts = validateAndCalculatePageShifts(pageSize);
// 省略HeadArena的构造...
if (nDirectArena > 0) {
// 创建DirectArena数组
directArenas = newArenaArray(nDirectArena);
List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(directArenas.length);
for (int i = 0; i < directArenas.length; i ++) {
PoolArena.DirectArena arena = new PoolArena.DirectArena(
this, pageSize, maxOrder, pageShifts, chunkSize, directMemoryCacheAlignment);
// 为每一个元素赋值
directArenas[i] = arena;
// arena实现了PoolArenaMetric,也添加到指标列表中
metrics.add(arena);
}
// 返回只读的列表
directArenaMetrics = Collections.unmodifiableList(metrics);
}
// PooledByteBufAllocator的测量指标
metric = new PooledByteBufAllocatorMetric(this);
}
下面继续分析对newHeadpBuffer的实现:
@Override
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
// 获取到PoolThreadLocalCache
PoolThreadCache cache = threadCache.get();
PoolArena<byte[]> heapArena = cache.heapArena;
final ByteBuf buf;
if (heapArena != null) {
// 真正的分配内存
buf = heapArena.allocate(cache, initialCapacity, maxCapacity);
} else {
buf = PlatformDependent.hasUnsafe() ?
new UnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
return toLeakAwareBuffer(buf);
}
可以看出真正的分配内存逻辑以及池化的操作是在PoolArena中。