在 Netty 的 PoolChunk
中,分配内存的核心逻辑是通过 allocateNode(int d)
方法实现的,其中 d
表示所需内存块在二叉树中的深度(层级)。以下是针对请求分配大小为 p
的内存块的完整流程和代码解析:
1. 确定所需内存的深度 d
首先需要将请求的内存大小 p
转换为对应的二叉树深度 d
:
-
规则:Netty 的
PoolChunk
采用完全二叉树管理内存,每个叶子节点代表一个 8KB 的内存块(默认chunkSize=16MB
,叶子节点数2048=16MB/8KB
)。 -
计算深度
d
:int normCapacity = normalizeSize(p); // 标准化请求大小(对齐到2的幂次) int d = maxOrder - (log2(normCapacity / pageSize)); // pageSize=8KB
-
请求
p=8KB
→d=11
(直接分配叶子节点) -
请求
p=16KB
→d=10
(需分配父节点,合并两个连续的8KB叶子节点)
-
2. 分配逻辑代码(简化版)
以下是 PoolChunk.allocateNode(int d)
的核心代码流程:
long allocateNode(int d) {
int id = 1; // 从根节点开始查找
int initial = -(1 << d); // 计算掩码,用于快速跳过不可用分支
byte val = memoryMap[id]; // 获取当前节点的值
if (val > d) { // 当前节点无法满足要求
return -1; // 分配失败
}
// 递归查找满足深度d的节点
while (val < d || (id & initial) == 0) {
id <<= 1; // 往下走一层,尝试左子节点
val = memoryMap[id];
if (val > d) { // 左子节点不满足,尝试右子节点
id ^= 1; // 切换到右子节点
val = memoryMap[id];
}
}
byte value = memoryMap[id];
assert value == d && (id & initial) == 1 << d : String.format("val=%d, d=%d, id=%d", value, d, id);
memoryMap[id] = maxOrder + 1; // 标记当前节点为已分配
updateParentsAlloc(id); // 更新父节点的分配状态
freeBytes -= runLength(id); // 减少剩余可用内存
return id; // 返回分配到的节点ID
}
3. 辅助方法说明
(1) updateParentsAlloc(int id)
更新父节点的 memoryMap
值,确保其反映子节点的最新状态:
private void updateParentsAlloc(int id) {
while (id > 1) {
int parentId = id >>> 1;
byte val1 = memoryMap[id];
byte val2 = memoryMap[id ^ 1]; // 兄弟节点
memoryMap[parentId] = (byte) Math.min(val1, val2); // 取子节点的最小值
id = parentId;
}
}
(2) runLength(int id)
计算节点 id
对应的内存块大小:
private int runLength(int id) {
return 1 << log2ChunkSize - depth(id); // 2^(log2(16MB) - depth)
}
(3) normalizeSize(int size)
标准化请求大小(对齐到2的幂次):
private static int normalizeSize(int size) {
if (size >= chunkSize) return chunkSize;
return Integer.highestOneBit(size); // 取最接近的2的幂次
}