Netty 之内存分配:PoolChunkList


#PoolChunkList

下面的代码给出了块链表PoolChunkList)中的字段,及其注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static final 
Iterator<PoolChunkMetric> 
EMPTY_METRICS = Collections.<PoolChunkMetric>emptyList().iterator();
// 所属的 arena
private final PoolArena<T> arena;
// arena 中 PoolChunkList 双链表中的后驱节点
private final PoolChunkList<T> nextList;
// 最小使用率
private final int minUsage;
// 最大使用率
private final int maxUsage;
// 最大分配容量
private final int maxCapacity;
// 块链表的首指针
private PoolChunk<T> head;
// arena 中 PoolChunkList 双链表中前驱节点
private PoolChunkList<T> prevList;

在构造块链表实例的时候,需要初始化下面的字段:

  • arena,所属的PoolArena
  • nextList,后驱节点;
  • minUsage,最小使用率;
  • maxUsage,最大使用率;
  • maxCapacity,最大分配容量,。块加入某个块链表时,它的使用量肯定大于或等于该块链表的最低使用率minUsage,因此,它所能分配的最大空间率只能是。通过maxCapacity,我们可以快速判断请求的空间当前块链表能不能满足,而不需要继续去遍历块链表中的块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, 
        int minUsage, int maxUsage, int chunkSize) {
    assert minUsage <= maxUsage;
    this.arena = arena;
    this.nextList = nextList;
    this.minUsage = minUsage;
    this.maxUsage = maxUsage;
    // 根据 minUsage 和 chunkSize 计算最大分配容量
    maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
}
// 计算最大分配容量
private static int calculateMaxCapacity(int minUsage, int chunkSize) {
    // 最低使用率 1
    minUsage = minUsage0(minUsage);
    if (minUsage == 100) {
        // 最小使用率 100,啥也分配不了,GG
        return 0;
    }
    // 最大分配容量为 chunkSize * ((100 - minUsage) / 100)
    return  (int) (chunkSize * (100L - minUsage) / 100L);
}

#allocate

利用块链表中的块给传入的PooledByteBuf实例分配并初始化底层空间。如果块的空间使用率超出当前块链表的设定的最大空间使用率,则把块顺着PoolArena中的链表往后移动到最大使用率更高的块链表中。

返回 TRUE,分配成功;FALSE,分配失败。

块内空间分配见 Netty 之内存分配:Buddy 算法Netty 之内存分配:Slab 算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
    if (head == null || normCapacity > maxCapacity) {
        // PoolChunkList 为空,
        // 或者规范化后的请求容量超过 PoolChunkList 能分配的最大值 maxCapacity,
        // 返回分配失败
        return false;
    }
    // 从 PoolChunk链表头开始分配空间,直到成功 or 全部失败
    for (PoolChunk<T> cur = head;;) {
        // 块内空间分配
        long handle = cur.allocate(normCapacity);
        // 分配失败
        if (handle < 0) {
            // 取下一个块
            cur = cur.next;
            // 没有下一个了,失败,GG
            if (cur == null) {
                return false;
            }
        } 
        // 当前块中空间分配成功
        else {
            // 初始化 buf 底层空间
            cur.initBuf(buf, handle, reqCapacity);
            // 当前块的使用量超出最大使用量了
            if (cur.usage() >= maxUsage) {
                // 从当前块链表中删除块 cur
                remove(cur);
                // 把块 cur 加入下一个 块链表 中
                nextList.add(cur);
            }
            return true;
        }
    }
}
// 从块链表内部的双链表中删除该块
private void remove(PoolChunk<T> cur) {
    if (cur == head) {
        head = cur.next;
        if (head != null) {
            head.prev = null;
        }
    } else {
        PoolChunk<T> next = cur.next;
        cur.prev.next = next;
        if (next != null) {
            next.prev = cur.prev;
        }
    }
}

#free

释放一个块节点页内空间。随着空间的回收,块的空间使用率下降,这时可能需要把当前块顺着PoolArena中的链表往前移动到最小使用率更低的块链表中。如果块的使用率为 0,该块将会被销毁。

返回值:

  • TRUE,说明回收成功;
  • FALSE,说明回收成功,且块完全空闲,需要销毁。

handle块节点编号和页内空间编号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
boolean free(PoolChunk<T> chunk, long handle) {
    // 释放块中节点或页内空间
    chunk.free(handle);
    // 低于最小使用率,需要挪块链表
    if (chunk.usage() < minUsage) {
        // 从当前的双链表中删除该块
        remove(chunk);
        // 水往低处流啊
        return move0(chunk);
    }
    // 不需要挪块链表
    return true;
}

private boolean move0(PoolChunk<T> chunk) {
    if (prevList == null) {
        // 没有前驱节点,只有 q000了
        assert chunk.usage() == 0;
        // 返回 false 将导致该 chunk 内存被销毁
        return false;
    }
    // 考虑往前移动到内存使用率低的块链表
    return prevList.move(chunk);
}

private boolean move(PoolChunk<T> chunk) {
    assert chunk.usage() < maxUsage;
    // 还能更低
    if (chunk.usage() < minUsage) {
        // 继往低处续流啊
        return move0(chunk);
    }
    // 目的地到了,加进来吧
    add0(chunk);
    return true;
}
// 块入块链表
void add0(PoolChunk<T> chunk) {
    chunk.parent = this;
    if (head == null) {
        head = chunk;
        chunk.prev = null;
        chunk.next = null;
    } else {
        chunk.prev = null;
        chunk.next = head;
        head.prev = chunk;
        head = chunk;
    }
}