code-learning/netty/45-Netty 源码解析-Buffer 之 Jemalloc(四)PoolChunkList.md

573 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 Netty 源码解析 —— Buffer 之 JemallocPoolChunkList
# 1. 概述
在 [《精尽 Netty 源码解析 —— Buffer 之 JemallocPoolChunk》](http://svip.iocoder.cn/Netty/ByteBuf-3-2-Jemalloc-chunk) ,我们看到 PoolChunk 有如下三个属性:
```
/**
* 所属 PoolChunkList 对象
*/
PoolChunkList<T> parent;
/**
* 上一个 Chunk 对象
*/
PoolChunk<T> prev;
/**
* 下一个 Chunk 对象
*/
PoolChunk<T> next;
```
- 通过 `prev``next` 两个属性,形成一个**双向** Chunk 链表 `parent`( PoolChunkList )。
那么为什么需要有 PoolChunkList 这样一个链表呢?直接开始撸代码。
# 2. PoolChunkList
`io.netty.buffer.PoolChunkList` ,实现 PoolChunkListMetric 接口,负责管理多个 Chunk 的生命周期,**在此基础上对内存分配进行进一步的优化**。
## 2.1 构造方法
```
/**
* 所属 PoolArena 对象
*/
private final PoolArena<T> arena;
/**
* 下一个 PoolChunkList 对象
*/
private final PoolChunkList<T> nextList;
/**
* Chunk 最小内存使用率
*/
private final int minUsage;
/**
* Chunk 最大内存使用率
*/
private final int maxUsage;
/**
* 每个 Chunk 最大可分配的容量
*
* @see #calculateMaxCapacity(int, int) 方法
*/
private final int maxCapacity;
/**
* PoolChunk 头节点
*/
private PoolChunk<T> head;
/**
* 前一个 PoolChunkList 对象
*/
// This is only update once when create the linked like list of PoolChunkList in PoolArena constructor.
private PoolChunkList<T> prevList;
// TODO: Test if adding padding helps under contention
//private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
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;
// 计算 maxUsage 属性
maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
}
```
- `arena` 属性,所属 PoolArena 对象。
- `prevList` + `nextList` 属性,上一个和下一个 PoolChunkList 对象。也就是说PoolChunkList 除了**自身**有一条双向链表外PoolChunkList 和 PoolChunkList **之间**也形成了一条双向链表。如下图所示:
> FROM [《深入浅出Netty内存管理 PoolChunkList》](https://www.jianshu.com/p/a1debfe4ff02)
>
> [![双向链表](45-Netty 源码解析-Buffer 之 JemallocPoolChunkList.assets/01.png)](http://static.iocoder.cn/images/Netty/2018_09_10/01.png)双向链表
- `head` 属性PoolChunkList **自身**的双向链表的**头节点**
- ```
minUsage
```
+
```
maxUsage
```
属性PoolChunkList 管理的 Chunk 们的内存使用率。
- 当 Chunk 分配的内存率超过 `maxUsage` 时,从当前 PoolChunkList 节点移除,添加到下一个 PoolChunkList 节点( `nextList` )。TODO 详细解析。
- 当 Chunk 分配的内存率小于 `minUsage` 时,从当前 PoolChunkList 节点移除,添加到上一个 PoolChunkList 节点( `prevList` )。TODO 详细解析。
- `maxCapacity` 属性,每个 Chunk 最大可分配的容量。通过 `#calculateMaxCapacity(int minUsage, int chunkSize)` 方法,来计算。代码如下:
```
/**
* Calculates the maximum capacity of a buffer that will ever be possible to allocate out of the {@link PoolChunk}s
* that belong to the {@link PoolChunkList} with the given {@code minUsage} and {@code maxUsage} settings.
*/
private static int calculateMaxCapacity(int minUsage, int chunkSize) {
// 计算 minUsage 值
minUsage = minUsage0(minUsage);
if (minUsage == 100) {
// If the minUsage is 100 we can not allocate anything out of this list.
return 0;
}
// Calculate the maximum amount of bytes that can be allocated from a PoolChunk in this PoolChunkList.
//
// As an example:
// - If a PoolChunkList has minUsage == 25 we are allowed to allocate at most 75% of the chunkSize because
// this is the maximum amount available in any PoolChunk in this PoolChunkList.
return (int) (chunkSize * (100L - minUsage) / 100L);
}
// 保证最小 >= 1
private static int minUsage0(int value) {
return max(1, value);
}
```
- 为什么使用 `(int) (chunkSize * (100L - minUsage) / 100L)` 来计算呢?因为 Chunk 进入当前 PoolChunkList 节点,意味着 Chunk 内存已经分配了 `minUsage` 比率,所以 Chunk 剩余的容量是 `chunkSize * (100L - minUsage) / 100L` 。😈 是不是豁然开朗噢?!
## 2.2 allocate
> 随着 Chunk 中 Page 的不断分配和释放,会导致很多碎片内存段,大大增加了之后分配一段连续内存的失败率。针对这种情况,可以把内存使用率较大的 Chunk 放到PoolChunkList 链表更后面。
`#allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity)` 方法,给 PooledByteBuf 对象分配内存块,并返回是否分配内存块成功。代码如下:
```
1: boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
2: // 双向链表中无 Chunk
3: // 申请分配的内存超过 ChunkList 的每个 Chunk 最大可分配的容量
4: if (head == null || normCapacity > maxCapacity) {
5: // Either this PoolChunkList is empty or the requested capacity is larger then the capacity which can
6: // be handled by the PoolChunks that are contained in this PoolChunkList.
7: return false;
8: }
9:
10: // 遍历双向链表。注意,遍历的是 ChunkList 的内部双向链表。
11: for (PoolChunk<T> cur = head;;) {
12: // 分配内存块
13: long handle = cur.allocate(normCapacity);
14: // 分配失败
15: if (handle < 0) {
16: // 进入下一节点
17: cur = cur.next;
18: // 若下一个节点不存在,返回 false ,结束循环
19: if (cur == null) {
20: return false; // 分配失败
21: }
22: // 分配成功
23: } else {
24: // 初始化内存块到 PooledByteBuf 对象中
25: cur.initBuf(buf, handle, reqCapacity);
26: // 超过当前 ChunkList 管理的 Chunk 的内存使用率上限
27: if (cur.usage() >= maxUsage) {
28: // 从当前 ChunkList 节点移除
29: remove(cur);
30: // 添加到下一个 ChunkList 节点
31: nextList.add(cur);
32: }
33: return true; // 分配成功
34: }
35: }
36: }
```
- 第 2 至 8 行:双向链表中无 Chunk或者申请分配的内存超过 ChunkList 的每个 Chunk 最大可分配的容量,返回 `false` ,分配失败。
- 第 11 行:遍历双向链表。**注意,遍历的是 ChunkList 的内部双向链表**。
- 第 13 行:调用 `PoolChunk#allocate(normCapacity)` 方法,分配内存块。这块,可以结合 [《精尽 Netty 源码解析 —— Buffer 之 JemallocPoolChunk》「2.2 allocate」](http://svip.iocoder.cn/Netty/ByteBuf-3-2-Jemalloc-chunk) 在复习下。
- 第 15 至 17 行:分配失败,进入下一个节点。
- 第 18 至 21 行:若下一个节点不存在,返回 `false` ,分配失败。
- 第 22 至 25 行:分配成功,调用
```
PooledByteBuf##initBuf(PooledByteBuf<T> buf, long handle, int reqCapacity)
```
方法,初始化分配的内存块到 PooledByteBuf 中。这块,可以结合
《精尽 Netty 源码解析 —— Buffer 之 JemallocPoolChunk》「2.5 initBuf」
在复习下。
- 第 26 至 32 行:超过当前 ChunkList 管理的 Chunk 的内存使用率上限,从当前 ChunkList 节点移除,并添加到“
”一个 ChunkList 节点。
- 第 29 行:调用 `#remove(PoolChunk<T> cur)` 方法,解析见 [「2.4.2 remove」](https://svip.iocoder.cn/Netty/ByteBuf-3-4-Jemalloc-chunkList/#) 。
- 第 31 行:调用 `#remove(PoolChunk<T> cur)` 方法,解析见 [「2.4.1 add」](https://svip.iocoder.cn/Netty/ByteBuf-3-4-Jemalloc-chunkList/#) 。
- 第 33 行:返回 `true` ,分配成功。
## 2.3 free
`#free(PoolChunk<T> chunk, long handle)` 方法,释放 PoolChunk 的指定位置( `handle` )的内存块。代码如下:
```
1: boolean free(PoolChunk<T> chunk, long handle) {
2: // 释放 PoolChunk 的指定位置( handle )的内存块
3: chunk.free(handle);
4: // 小于当前 ChunkList 管理的 Chunk 的内存使用率下限
5: if (chunk.usage() < minUsage) {
6: // 从当前 ChunkList 节点移除
7: remove(chunk);
8: // 添加到上一个 ChunkList 节点
9: // Move the PoolChunk down the PoolChunkList linked-list.
10: return move0(chunk);
11: }
12: // 释放成功
13: return true;
14: }
```
- 第 3 行:调用 `PoolChunk#free(long handle)` 方法,释放指定位置的内存块。这块,可以结合 [《精尽 Netty 源码解析 —— Buffer 之 JemallocPoolChunk》「2.3 free」](http://svip.iocoder.cn/Netty/ByteBuf-3-2-Jemalloc-chunk) 在复习下。
- 第 5 行:小于当前 ChunkList 管理的 Chunk 的内存使用率下限:
- 第 7 行:调用 `#remove(PoolChunk<T> cur)` 方法,从当前 ChunkList 节点移除。
- 第 10 行:调用 `#move(PoolChunk<T> chunk)` 方法, 添加到“上”一个 ChunkList 节点。详细解析,见 [「2.4.3 move」](https://svip.iocoder.cn/Netty/ByteBuf-3-4-Jemalloc-chunkList/#) 。
- 第 13 行:返回 `true` ,释放成功。
## 2.4 双向链表操作
### 2.4.1 add
`#add(PoolChunk<T> chunk)` 方法,将 PoolChunk 添加到 ChunkList 节点中。代码如下:
```
1: void add(PoolChunk<T> chunk) {
2: // 超过当前 ChunkList 管理的 Chunk 的内存使用率上限,继续递归到下一个 ChunkList 节点进行添加。
3: if (chunk.usage() >= maxUsage) {
4: nextList.add(chunk);
5: return;
6: }
7: // 执行真正的添加
8: add0(chunk);
9: }
```
- 第 2 至 6 行:超过当前 ChunkList 管理的 Chunk 的内存使用率上限,调用 `nextList` 的 `#add(PoolChunk<T> chunk)` 方法,继续递归到下一个 ChunkList 节点进行添加。
- 第 8 行:调用 `#add0(PoolChunk<T> chunk)` 方法,执行真正的添加。代码如下:
```
/**
* Adds the {@link PoolChunk} to this {@link PoolChunkList}.
*/
void add0(PoolChunk<T> chunk) {
chunk.parent = this;
// <1> 无头节点,自己成为头节点
if (head == null) {
head = chunk;
chunk.prev = null;
chunk.next = null;
// <2> 有头节点,自己成为头节点,原头节点成为自己的下一个节点
} else {
chunk.prev = null;
chunk.next = head;
head.prev = chunk;
head = chunk;
}
}
```
- `<1>` 处,比较好理解,胖友自己看。
- `<2>` 处,因为 `chunk` **新**进入下一个 ChunkList 节点,一般来说,内存使用率相对较低,分配内存块成功率相对较高,所以变成新的首节点。
### 2.4.2 remove
`#remove(PoolChunk<T> chunk)` 方法,从当前 ChunkList 节点移除。代码如下:
```
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;
}
}
}
```
- 代码比较简单,胖友自己研究。
### 2.4.3 move
`#move(PoolChunk<T> chunk)` 方法, 添加到“上”一个 ChunkList 节点。代码如下:
```
/**
* Moves the {@link PoolChunk} down the {@link PoolChunkList} linked-list so it will end up in the right
* {@link PoolChunkList} that has the correct minUsage / maxUsage in respect to {@link PoolChunk#usage()}.
*/
1: private boolean move(PoolChunk<T> chunk) {
2: assert chunk.usage() < maxUsage;
3:
4: // 小于当前 ChunkList 管理的 Chunk 的内存使用率下限,继续递归到上一个 ChunkList 节点进行添加。
5: if (chunk.usage() < minUsage) {
6: // Move the PoolChunk down the PoolChunkList linked-list.
7: return move0(chunk);
8: }
9:
10: // 执行真正的添加
11: // PoolChunk fits into this PoolChunkList, adding it here.
12: add0(chunk);
13: return true;
14: }
```
- 第 4 至 8 行:小于当前 ChunkList 管理的 Chunk 的内存使用率下限,调用 `#move0(PoolChunk<T> chunk)` 方法,继续递归到上一个 ChunkList 节点进行添加。代码如下:
```
private boolean move(PoolChunk<T> chunk) {
assert chunk.usage() < maxUsage;
// 小于当前 ChunkList 管理的 Chunk 的内存使用率下限,继续递归到上一个 ChunkList 节点进行添加。
if (chunk.usage() < minUsage) {
// Move the PoolChunk down the PoolChunkList linked-list.
return move0(chunk);
}
// 执行真正的添加
// PoolChunk fits into this PoolChunkList, adding it here.
add0(chunk);
return true;
}
```
- 第 12 行:调用 `#add0(PoolChunk<T> chunk)` 方法,执行真正的添加。
- 第 13 行:返回 `true` ,移动成功。
## 2.5 iterator
`#iterator()` 方法,创建 Iterator 对象。代码如下:
```
private static final Iterator<PoolChunkMetric> EMPTY_METRICS = Collections.<PoolChunkMetric>emptyList().iterator();
@Override
public Iterator<PoolChunkMetric> iterator() {
synchronized (arena) {
// 空,返回 EMPTY_METRICS
if (head == null) {
return EMPTY_METRICS;
}
// 生成数组,后生成 Iterator
List<PoolChunkMetric> metrics = new ArrayList<PoolChunkMetric>();
for (PoolChunk<T> cur = head;;) {
metrics.add(cur);
cur = cur.next;
if (cur == null) {
break;
}
}
return metrics.iterator();
}
}
```
## 2.6 destroy
`#destroy()` 方法,销毁。代码如下:
```
void destroy(PoolArena<T> arena) {
// 循环,销毁 ChunkList 管理的所有 Chunk
PoolChunk<T> chunk = head;
while (chunk != null) {
arena.destroyChunk(chunk);
chunk = chunk.next;
}
// 置空
head = null;
}
```
## 2.7 PoolChunkListMetric
`io.netty.buffer.PoolChunkListMetric` ,继承 Iterable 接口PoolChunkList Metric 接口。代码如下:
```
public interface PoolChunkListMetric extends Iterable<PoolChunkMetric> {
/**
* Return the minimum usage of the chunk list before which chunks are promoted to the previous list.
*/
int minUsage();
/**
* Return the maximum usage of the chunk list after which chunks are promoted to the next list.
*/
int maxUsage();
}
```
------
PoolChunkList 对 PoolChunkMetric 接口的实现,代码如下:
```
@Override
public int minUsage() {
return minUsage0(minUsage);
}
@Override
public int maxUsage() {
return min(maxUsage, 100);
}
```
# 3. PoolChunkList 初始化
在 PoolChunkArena 中,初始化 PoolChunkList 代码如下:
```
// PoolChunkList 之间的双向链表
private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
/**
* PoolChunkListMetric 数组
*/
private final List<PoolChunkListMetric> chunkListMetrics;
1: protected PoolArena(PooledByteBufAllocator parent, int pageSize,
2: int maxOrder, int pageShifts, int chunkSize, int cacheAlignment) {
3:
4: // ... 省略其它无关代码
5:
6: // PoolChunkList 之间的双向链表,初始化
7:
8: q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, chunkSize);
9: q075 = new PoolChunkList<T>(this, q100, 75, 100, chunkSize);
10: q050 = new PoolChunkList<T>(this, q075, 50, 100, chunkSize);
11: q025 = new PoolChunkList<T>(this, q050, 25, 75, chunkSize);
12: q000 = new PoolChunkList<T>(this, q025, 1, 50, chunkSize);
13: qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, chunkSize);
14:
15: q100.prevList(q075);
16: q075.prevList(q050);
17: q050.prevList(q025);
18: q025.prevList(q000);
19: q000.prevList(null); // 无前置节点
20: qInit.prevList(qInit); // 前置节点为自己
21:
22: // 创建 PoolChunkListMetric 数组
23: List<PoolChunkListMetric> metrics = new ArrayList<PoolChunkListMetric>(6);
24: metrics.add(qInit);
25: metrics.add(q000);
26: metrics.add(q025);
27: metrics.add(q050);
28: metrics.add(q075);
29: metrics.add(q100);
30: chunkListMetrics = Collections.unmodifiableList(metrics);
31: }
```
- PoolChunkList 之间的双向链表有 `qInit`、`q000`、`q025`、`q050`、`q075`、`q100` 有 6 个节点,在【第 6 至 20 行】的代码,进行**初始化**。链表如下:
```
// 正向
qInit -> q000 -> q025 -> q050 -> q075 -> q100 -> null
// 逆向
null <- q000 <- q025 <- q050 <- q075 <- q100
qInit <- qInit
```
- 比较神奇的是,
```
qInit
```
指向自己?!
```
qInit
```
用途是,新创建的 Chunk 内存块
```
chunk_new
```
( 这只是个代号,方便描述 ) ,添加到
```
qInit
```
后,不会被释放掉。
- 为什么不会被释放掉?`qInit.minUsage = Integer.MIN_VALUE` ,所以在 `PoolChunkList#move(PoolChunk chunk)` 方法中,`chunk_new` 的内存使用率最小值为 0 ,所以肯定不会被释放。
- 那岂不是 `chunk_new` 无法被释放?随着 `chunk_new` 逐渐分配内存,内存使用率到达 25 ( `qInit.maxUsage` )后,会移动到 `q000` 。再随着 `chunk_new` 逐渐释放内存,内存使用率降到 0 (`q000.minUsage`) 后,就可以被释放。
- 当然,如果新创建的 Chunk 内存块 `chunk_new` **第一次**分配的内存使用率超过 25 ( `qInit.maxUsage` ),不会进入 `qInit` 中,而是进入后面的 PoolChunkList 节点。
- `chunkListMetrics` 属性PoolChunkListMetric 数组。在【第 22 至 30 行】的代码,进行**初始化**。
# 666. 彩蛋
PoolChunList 相比 PoolSubpage 来说,又又又更加简单啦。
老艿艿整理了下 Arena、ChunkList、Chunk、Page、Subpage 的“操纵”关系如下图:
[![PoolSubpage](45-Netty 源码解析-Buffer 之 JemallocPoolChunkList.assets/02.png)](http://static.iocoder.cn/images/Netty/2018_09_10/02.png)PoolSubpage
- 当然,这不是一幅严谨的图,仅仅表达“操纵”的关系。
参考如下文章:
- Hypercube [《自顶向下深入分析NettyPoolChunkList》](https://www.jianshu.com/p/2b8375df2d1a)
- 占小狼 [《深入浅出Netty内存管理 PoolChunkList》](https://www.jianshu.com/p/4856bd30dd56)