Netty学习之旅----源码分析内存分配与释放原理

本文详细分析了Netty中PoolChunk的内存分配与释放原理,从PoolChunk的allocate方法开始,逐步讲解了PoolSubpage的创建、分配和释放过程,包括PoolSubpage在PoolChunk内存映射中的位置计算、内存分配算法以及释放内存时的PoolArena和PoolChunk的角色。文章深入探讨了PoolChunk的内存管理策略,包括PoolSubpage的bitmap索引计算和内存分配的细节,为理解Netty内存管理提供了清晰的路径。
摘要由CSDN通过智能技术生成

if (id < 0) {

return id;

}

final PoolSubpage[] subpages = this.subpages;

final int pageSize = this.pageSize;

freeBytes -= pageSize;

int subpageIdx = subpageIdx(id); // @2

PoolSubpage subpage = subpages[subpageIdx];

if (subpage == null) {

subpage = new PoolSubpage(this, id, runOffset(id), pageSize, normCapacity); // 代码@3

subpages[subpageIdx] = subpage;

} else { //@4

subpage.init(normCapacity);

}

return subpage.allocate(); //代码@5

}

首先讲解一下整体分配思路,先根据PoolChunk内部维护的各个PoolChunk的占用情况,返回一个可以PoolChunk的id,这里的id就是memoryMap数组的下标。然后从PoolChunk维护的PoolSubpage数组对象中获取一个与之对应的PoolSubpage,如果为空,先创建一个PoolSubpage。然后再在PoolSubpage中分配内存。如果已经存在PoolSubpage,则直接分配。

代码@1:首先,我们需要从meomryMap中寻找一个可供分配的PoolSubpage。memoryMap就是记录整个PoolSubpage的占用情况。

代码@2:根据我们现在掌握的知识应该知道,memoryMap[ 2的maxOrder]存放的是第一个PoolSubpage,memoryMap[ 2的 (maxOrder+1)的幂再减去1]存放的是最后一个PoolSubpage,所以接下来我们需要将id转换为实际的PoolSubpage。从上面的讲解,其实 id 与 2的maxOrder的偏移,,比如基数2048对应0,2049对应1,基数是2048,根据id求数组中的下标应该清楚了吧,给出如下三个算法实现,请看下午的关于subpageIdx方法的详解。

代码@3:如果id对应的PoolSubpage没有被初始化,则新建一个PoolSubpage。在内存分配前置篇的时候特意讲解了PoolSubpage几个关键属性,当然包括这里的runOffset;在这里再重复一遍:在整个Netty内存管理中,其实真正持有内存的类是PoolChunk, 如果是直接内存,就是 java.nio.ByteBuffer memory;如果是堆内存的话,就是byte[] memory,我们以堆内存来讲解,比较直观,一个PoolChunk一旦创建,就会分配内存 byte[] memory = new byte[chunkSize];然后PoolChunk由一个一个的PoolSubpage组成,也就是PoolSubpage[] subpages;那们我们如果保存 memoryMap[id]位置代表的PoolSubPage的内存呢?使用一个偏移量,相对于PoolChunk的byte[] meomry的偏移量。那怎么计算呢?先得出id所在平衡二叉树的深度,就能得到该层有多个节点,所谓的偏移量,就是针对同级的。当然,真正的偏移量主要还是针对PoolSubpage,整个Netty的内存管理,真正涉及到内存的只有PoolChunk,PoolSubpage维护一个偏移量,其实就是一个指针,表示PoolChunk的T memory的(从runOffset 至 (runOffset+pageSize)被这个PoolSubpage占用。

代码@5:PoolSubpage 的具体分配方法。

1.3.1.1 关于代码@1PoolChunk allocateSubpage 的allocateNode的详细解读如下

private int allocateNode(int d) {

int id = 1;

int initial = - (1 << d); // has last d bits = 0 and rest all = 1

byte val = value(id);

if (val > d) { // unusable

return -1;

}

while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0 //@1

id <<= 1; //@2

val = value(id); //@3

if (val > d) { //@4

id ^= 1;

val = value(id);

}

}

byte value = value(id);

assert value == d && (id & initial) == 1 << d : String.format(“val = %d, id & initial = %d, d = %d”,

value, id & initial, d);

setValue(id, unusable); // mark as unusable //@5

updateParentsAlloc(id); //@6

return id;

}

private byte value(int id) {

return memoryMap[id];

}

这里再重复一次PoolChunk里面维护一颗平衡二叉树来映射PoolChunk每个PoolSubpage的是否已经被分配的情况,平衡二叉树的深度为maxOrder,也就是平衡二叉数的所有叶子节点代表所有的PoolSubpage,而整棵二叉树从根到下,从左到右被映射为一个数组memoryMap,memoryMap长度为( 2的 (maxOrder=1)),memoryMap[0]处不存放任何元素,从 id=1到 2的(maxOrder-1)幂-1,举个更直观的例子,比如maxOrder为10,则memoryMap[]的长度为2048,memoryMap[1] 存放平衡二叉树的根节点,memory[2],memory[3]存放深度为1的节点,依次类推,memoryMap[2047]存放最后一个叶子节点。具体的算法是从根节点(dept=0)开始,一直向下找,直到到达层为d。找合适的节点。

代码@2:找到id的左子树(左节点)。

代码@3:获取该id所在二叉树的深度。

代码@4:val > d 表示已经被占用,需要找id的兄弟,也就是右节点,在平衡二叉树,已知左节点,求右节点的下标,使用id^1即可,然后看右节点是否被占用,如果没有,直接返回。理解这个算法,要知道,如果memoryMap[id]不可用,则不会继续去查找其子节点,也就是说,如果memoryMap[id]可用,就代表了至少有一个节点是没有被占用的。如果全被占用,id的值会为 2的(maxOrder+1)的幂,再减去1,如果按照上面的例子,id=2047。

代码@5:将memoryMap[id]=unusable,(maxOrder+1),代表PoolSubpage已经被占用。

代码@6:递归更新父节点的占用情况,这里有个哲学,memoryMap[id]中存的值,代表可以至少可以分配的深度数,比如memoryMap[8] = 2,则表示id为8的节点可以胜任分配深度>=2的内存大小,我们知道,深度越大,节点代表的内存就越小。比如id=3的节点,最小可以分配的深度为2,那他的子节点id为(6,7)能分配的最小深度为3,那比如id=6的节点已被分配,那它的直接父节点id=3,此时就不能再分配深度为2的内存了,因为已经被占用了一半,只能分配深度为3。updateParentsAlloc方法就是按照上述思路实现的,具体情况分析如下:

private void updateParentsAlloc(int id) {

while (id > 1) {// @1

int parentId = id >>> 1; // @2

byte val1 = value(id); //@3

byte val2 = value(id ^ 1); //@4

byte val = val1 < val2 ? val1 : val2; //@5

setValue(parentId, val);

id = parentId;

}

}

代码@1:循环调用,知道根节点。

代码@2:找到父节点。

代码@3,@4:得到parentId两个子节点当前可分配的深度值

代码@5:父节点可分配的深度值为两个子节点可分配的深度值的最小值。

1.3.1.2 PoolChunk的allocateSubpage代码@2 的 subpageIdx方法:

private int subpageIdx(int memoryMapIdx) {

return memoryMapIdx ^ maxSubpageAllocs; // remove highest set bit, to get offset

//我们一般简单的实现:

//return memoryMapIdx - maxSubpageAllocs;

//或 return memoryMapIdx & ( ~( (1 << maxOrder) -1) );

}

1.3.1.3 PoolChunk的allocateSubpage代码@5 poolSubpage.allocate方法详解:

/**

  • Returns the bitmap index of the subpage allocation.

*/

long allocate() {

if (elemSize == 0) {

return toHandle(0);

}

if (numAvail == 0 || !doNotDestroy) { //@1

return -1;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值