吃透Netty源码系列二十四之池化内存分配五
PoolChunk的initBuf
上一篇我们讲到子页的内存分配,讲到了我们获取了handle
,今天将获取之后做什么。
我们可以看到子页分配的都是那么大的数,原因上篇已经讲过了。后面会有获取nioBuffer
,就是在PoolChunk
构造函数中创建的。
接下去下去就是initBuf
方法了。
void initBuf(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity) {
int memoryMapIdx = memoryMapIdx(handle);//低32位 即内存映射索引
int bitmapIdx = bitmapIdx(handle);//这里获取的如果是子页的handle,bitmapIdx不为0,那高32位,并不是真正的位图索引,最高非符号位多了1,如果是normal的,那就是0
if (bitmapIdx == 0) {//normal只有id,没有前面的子页相关的位图信息
byte val = value(memoryMapIdx);
assert val == unusable : String.valueOf(val);
buf.init(this, nioBuffer, handle, runOffset(memoryMapIdx) + offset,
reqCapacity, runLength(memoryMapIdx), arena.parent.threadCache());
} else {//子页初始化
initBufWithSubpage(buf, nioBuffer, handle, bitmapIdx, reqCapacity);
}
}
//获取低32位的handle,即id
private static int memoryMapIdx(long handle) {
return (int) handle;
}
//直接获取handle高32位,子页的并非原始的bitmapIdx
private static int bitmapIdx(long handle) {
return (int) (handle >>> Integer.SIZE);
}
//获取对应编号的深度索引
private byte value(int id) {
return memoryMap[id];
}
其实就是根据bitmapIdx
来区分是子页的还是Normal
的初始化。
PoolChunk的initBufWithSubpage
这里直接拿子页的讲,因为不是子页的也是调用了相同的buf.init
方法,只是参数不一样。
private void initBufWithSubpage(PooledByteBuf<T> buf, ByteBuffer nioBuffer,
long handle, int bitmapIdx, int reqCapacity) {
assert bitmapIdx != 0;
int memoryMapIdx = memoryMapIdx(handle);//获取内存映射索引
PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];//获取对应的子页
assert subpage.doNotDestroy;//还没销毁
assert reqCapacity <= subpage.elemSize;//请求容量不会大于规范后的
//池化字节缓冲区初始化
buf.init(
this, nioBuffer, handle,
runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset,//块的偏移地址+页的偏移地址+缓存行的偏移地址(默认0)
reqCapacity, subpage.elemSize, arena.parent.threadCache());
}
我们可以看到,子页会取出subpage
,因为下面会需要计算chunk
块的偏移,也就是分配的内存在chunk
块内部数组的其实地址。
runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset
我们来分析下这个地址什么意思。
runOffset(memoryMapIdx)
这个前面分析过,其实就是对应的页偏移地址,一页是8k
嘛,也就是8k
的整数倍。
(bitmapIdx & 0x3FFFFFFF) * subpage.elemSize
这个里面的(bitmapIdx & 0x3FFFFFFF)
就是对求handle
时候用到的0x4000000000000000L
解码啦,解码出子页真正的bitmapIdx
,也就是子页内存的偏移位置,然后在乘以每块内存的大小subpage.elemSize
,就是在子页中的偏移位置。
offset
这个是缓存行的偏移,一般用不到,一个缓存行里放多个数据可能就要用到啦,不过这样可能会引起多线程竞争了,不过netty
的这个内存分配方案,就是为了最大程度避免多线程的竞争,这个暂时就不多说了。
所以他们加起来就是在chunk
块中的偏移,也就可以对应到chunk
块中的memory
上去了,最终都是字节数组。
我画个图吧,假设子页已经分配了64B
的内存,即块上的字节数组用了64B
,然后又申请了16B
,最后的块偏移地址就是64
,其实意思就是说从数组索引64
开始,长度为16
的分配给我用了。
如果对应的不是子页的话,就会少算一个子页内存的偏移,当然此时的分配大小就是runLength(memoryMapIdx)
,即某个节点的大小。
PooledByteBuf的init
最终会调用到init0
:
private void init0(PoolChunk<T> chunk, ByteBuffer nioBuffer,
long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
assert handle >= 0;
assert chunk != null;
this.chunk = chunk;//哪个块
memory = chunk.memory;//字节数组,或者内存地址
tmpNioBuf = nioBuffer;//缓存的buffer
allocator = chunk.arena.parent;//分配器
this.cache = cache;//线程缓存
this.handle = handle;//句柄
this.offset = offset;//缓存行偏移
this.length = length;//申请的内存大小
this.maxLength = maxLength;//规范化后的内存大小
}
至此分配成功了,之后就返回:
需要把新的块加到块列表qInit
中。其他的一些不重要的暂时不讲了。我们回顾下,整一个分配内存其实就是做了一件事,就是告诉我,我分配的内存在哪个块的字节数的哪个便宜位置上,可以用多少长度。
大致的流程讲完了,当然还有其他很多细节,我会慢慢补充的,下面来看下申请完了后,一些属性的变化。
看看这句代码执行后的情况:
ByteBuf byteBuf= ByteBufAllocator.DEFAULT.heapBuffer(5);
子页的一些属性:
PoolArena的allocateHuge
申请chunk
块大小内的我们都讲了,现在讲下超过16MB
的怎么处理的。
private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
PoolChunk<T> chunk = newUnpooledChunk(reqCapacity);//申请容量不用规划话,直接创建
activeBytesHuge.add(chunk.chunkSize());//添加已用字节
buf.initUnpooled(chunk, reqCapacity);
allocationsHuge.increment();
}
PoolArena的newUnpooledChunk
创建的是无池化的chunk
。
@Override
protected PoolChunk<byte[]> newUnpooledChunk(int capacity) {
return new PoolChunk<byte[]>(this, newByteArray(capacity), capacity, 0);
}
PoolChunk的无池化构造方法
其实就是好多池化内存管理的属性都用不到了,unpooled = true
PoolChunk(PoolArena<T> arena, T memory, int size, int offset) {
unpooled = true;
this.arena = arena;
this.memory = memory;
this.offset = offset;
memoryMap = null;
depthMap = null;
subpages = null;
subpageOverflowMask = 0;
pageSize = 0;
pageShifts = 0;
maxOrder = 0;
unusable = (byte) (maxOrder + 1);
chunkSize = size;
log2ChunkSize = log2(chunkSize);
maxSubpageAllocs = 0;
cachedNioBuffers = null;
}
PooledByteBuf的initUnpooled
最终还是调用init0
,只是好多参数都是默认值,而且是没有缓存的。
申请内存的流程大致讲完了,后面还会讲下释放的时候是怎么样的,但是释放的时候会涉及到缓存,对象池,所以得先把缓存讲一下。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。