Netty 内存分配 ByteBuf
初识ByteBuf
ByteBuf是io和应用程序的中间层;读数据时,ByteBuf先从io中读取,在发送给应用程序;写数据时,ByteBuf先从应用程序中获取,再向io写入。
ByteBuf 的基本结构

3个区间
0-readerIndex(读区间的开始位置):丢弃的数据区
readerIndex-writerIndex(写区间的开始位置):可读数据区
writerIndex-capacity:可写数据区
容量
capactiy:当前buffer的容量
maxCapacity:可扩容的最大缓冲区的容量
ByteBuf 的重要 API
ByteBuf的基本实现是AbstractByteBuf
ByteBuf 的基本分类
Pooled(池化内存):基于预先分配好的内存空间
Unsafe:基于对象的内存地址进行读写操作,jdk底层负责io操作的对象
Direct(堆外内存):基于物理地址进行读写操作,不在jvm堆内存中,需要手动释放
综上所述, ByteBuf 一共会有六种组合:Pooled 池化内存和 Unpooled 非池化内存;Unsafe 和非 Unsafe;Heap 堆内存和 Direct 堆外内存

ByteBufAllocator 内存管理器
ByteBufAllocator 的基本实现类 AbstractByteBufAllocator
Unpooled 非池化内存分配
堆内内存的分配
heapBuffer 的分配逻辑
private UnpooledHeapByteBuf(
ByteBufAllocator alloc, byte[] initialArray, int readerIndex, int writerIndex, int maxCapacity) {
setArray(initialArray);
setIndex(readerIndex, writerIndex);
}
初始化一个数组
UnpooledUnsafeHeapByteBuf 和 UnpooledHeapByteBuf区别
根本区别在io读写
1 UnpooledHeapByteBuf
io.netty.buffer.UnpooledHeapByteBuf#getByte,直接从数组索引取值
static byte getByte(byte[] memory, int index) {
return memory[index];
}
2 UnpooledUnsafeHeapByteBuf
io.netty.buffer.UnsafeByteBufUtil#getByte(byte[], int)从unsafe取值
static byte getByte(byte[] data, int index) {
return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);
}
堆外内存的分配
UnpooledDirectByteBuf和UnpooledUnsafeDirectByteBuf区别
1 UnpooledDirectByteBuf
通过数组下标来取数据
2 UnpooledUnsafeDirectByteBuf
final void setByteBuffer(ByteBuffer buffer, boolean tryFree) {
this.buffer = buffer;
memoryAddress = PlatformDependent.directBufferAddress(buffer);
}
private static long getLong(Object object, long fieldOffset) {
return UNSAFE.getLong(object, fieldOffset);
}
unsafe直接通过buffer的内存地址和偏移量来取数据
Pooled 池化内存分配
PooledByteBufAllocator 简述
FastThreadLocal:通过数组来存储数据且执行性能较快的本地线程
PoolArenaMetric:池子的数据记录
protected synchronized PoolThreadCache initialValue() {
final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas);
final PoolArena<ByteBuffer> directArena = leastUsedArena(directArenas);
return new PoolThreadCache(
heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize,
DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);
}
DEFAULT_NUM_HEAP_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numHeapArenas",
(int) Math.min(
defaultMinNumArena,
runtime.maxMemory() / defaultChunkSize / 2 / 3)));
DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
SystemPropertyUtil.getInt(
"io.netty.allocator.numDirectArenas",
(int) Math.min(
defaultMinNumArena,
PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));
EventLoopGroup 给分配线程时默认线程数也是 CPU 核数2,area的默认值也是CPU 核数2,这样做是每个线程都有一个独立的Arena,保障在内存分配时不被加锁。

DirectArena 内存分配流程
Arena 分配内存的基本流程有三个步骤:
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
PooledByteBuf<T> buf = newByteBuf(maxCapacity);
allocate(cache, buf, reqCapacity);
return buf;
}
1、从对象池里拿到 PooledByteBuf 进行复用;
static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
PooledUnsafeDirectByteBuf buf = RECYCLER.get();
buf.reuse(maxCapacity);
return buf;
}
2、从缓存中进行内存分配;
3、从内存堆里进行内存分配。
内存池的内存规格
在 Netty 内存池中主要设置了四种规格大小 的内存:tiny 是指 0-512Byte 之间的规格大小,small 是指 512Byte-8KB 之间的规格大小,normal 是指 8KB-16MB 之 间的规格大小,huge 是指 16MB 以上.为什么 Netty 会选择这些值作为一个分界点呢?其实在 Netty 底层还有一个内 存单位的封装,为了更高效地管理内存,避免内存浪费,把每一个区间的内存规格由做了细分。默认情况下,Netty 将内存规格划分为 4 个部分。Netty 中所有的内存申请是以 Chunk 为单位向内存申请的,大小为 16M,后续的所有内 存分配都是在这个 Chunk 里面的操作。8K 对应的是一个 Page,一个 Chunk 会以 Page 为单位进行切分,8K 对应 Chunk 被划分为 2048 个 Page。小于 8K 的对应的是 SubPage。例如:我们申请的一段内存空间只有 1K,却给我们分配了一 个 Page,显然另外 7K 就会被浪费,所以就继续把 Page 进行划分,来节省空间.
