在这个类中我们主要从下面这个方法开始看:
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
//从当前线程中拿到一个cache对象
PoolThreadCache cache = threadCache.get();
//从cache中获得内存管理的顶层“竞技场”
PoolArena<ByteBuffer> directArena = cache.directArena;
ByteBuf buf;
//根据竞技场是否为空来选择不同申请空间的方式
if (directArena != null) {
buf = directArena.allocate(cache, initialCapacity, maxCapacity);
} else {
if (PlatformDependent.hasUnsafe()) {
buf = UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
} else {
buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
}
}
return toLeakAwareBuffer(buf);
}
在这个方法中一共做了三件事:
1、拿到线程局部缓存PoolThreadCache
2、从内存池中拿到一个管理内存的对象
3、根据这个对象是否为空来选择不同的申请内存的方式
我们进去看一下PoolThreadCache:
final class PoolThreadLocalCache extends FastThreadLocal<PoolThreadCache>
我们发现它继承自FastThreadLocal,他是Netty自己实现的ThreadLocal,他比jdk中的ThreadLocal更快。然后我们继续分析:
protected synchronized PoolThreadCache initialValue() {
final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas);
final PoolArena<ByteBuffer> directArena = leastUsedArena(directArenas);
//通过构造函数创建一个PoolThreadCache
return new PoolThreadCache(
heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize,
DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);
}
在这个方法中首先获取了两个对象,接着通过构造函数创建一个PoolThreadCache。
PoolArena<ByteBuffer> directArena = cache.directArena;
Netty所有关于内存分配的逻辑都是通过竞技场分配的,我们看一下这个竞技场是在什么时候被初始化的:
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
int tinyCacheSize, int smallCacheSize, int normalCacheSize) {
...
int pageShifts = validateAndCalculatePageShifts(pageSize);
if (nHeapArena > 0) {
//创建一个Arena数组
heapArenas = newArenaArray(nHeapArena);
List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
//将数组的每一个元素填充为HeapArena
for (int i = 0; i < heapArenas.length; i ++) {
PoolArena.HeapArena arena = new PoolArena.HeapArena(this, pageSize, maxOrder, pageShifts, chunkSize);
heapArenas[i] = arena;
metrics.add(arena);
}
heapArenaMetrics = Collections.unmodifiableList(metrics);
} else {
heapArenas = null;
heapArenaMetrics = Collections.emptyList();
}
if (nDirectArena > 0) {
//创建一个Arena数组
directArenas = newArenaArray(nDirectArena);
//将数组的每一个元素填充为DirectArena
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);
directArenas[i] = arena;
metrics.add(arena);
}
directArenaMetrics = Collections.unmodifiableList(metrics);
} else {
directArenas = null;
directArenaMetrics = Collections.emptyList();
}
}
我们发现这两种负责内存分配的竞技场是在内存分配器的构造函数中分配的,而数组的大小默认情况下是cpu核数的2倍,这正好和EventLoop默认的线程数目相同,也就是每一个线程都有一个独想的内存。
我们不仅可以通过PoolTheadCache里面的area进行内存分配,当有bytebuf释放内存后,Netty会把这块内存根据它的大小放到一个缓存中,下次有申请内存如果缓存中有对应大小的内存,那么就直接从缓存中分配。
tinySubPageDirectCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
smallSubPageDirectCaches = createSubPageCaches( smallCacheSize,directArena.numSmallSubpagePools, SizeClass.Small);
normalDirectCaches = createNormalCaches(normalCacheSize, maxCachedBufferCapacity, directArena);
这几种不同规格的缓存都是在构造方法里面被创建的,我们以tinySubPageDirectCaches 为例看一下:
private static <T> MemoryRegionCache<T>[] createSubPageCaches(
int cacheSize, int numCaches, SizeClass sizeClass) {
if (cacheSize > 0) {
@SuppressWarnings("unchecked")
//根据这种规格可被分割的内存的大小
MemoryRegionCache<T>[] cache = new MemoryRegionCache[numCaches];
for (int i = 0; i < cache.length; i++) {
// 为每种规格的缓存创建要给队列
cache[i] = new SubPageMemoryRegionCache<T>(cacheSize, sizeClass);
}
return cache;
} else {
return null;
}
}
小于512B的内存大小属于tiny级别的,Netty将tiny按16B一个间隔来划分,一公分了32个,所以cache的大小就是32,然后为每个空间创建一个队列。每当申请空间的时候,先把申请的空间变成大于它且离它最近的2的幂,然后根据这个大小选择对应的规格,先在缓存中查找,没有的话,就要通过area分配。在下一节中我们将详细介绍内存分配流程。