code-learning/netty/44-Netty 源码解析-Buffer 之 Jemalloc(三)PoolSubpage.md

798 lines
24 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 之 JemallocPoolSubpage
# 1. 概述
在 [《精尽 Netty 源码解析 —— Buffer 之 JemallocPoolChunk》](http://svip.iocoder.cn/Netty/ByteBuf-3-2-Jemalloc-chunk) 一文中,我们已经看到,为了进一步提供提高内存**分配效率**并减少**内存碎片**Jemalloc 算法将每个 Chunk 切分成多个**小块** Page 。
但是实际应用中Page 也是**比较大**的内存块如果直接使用明显是很浪费的。因此Jemalloc 算法将每个 Page 更进一步的切分为**多个** Subpage 内存块。Page 切分成**多个** Subpage 内存块,并未采用相对复杂的算法和数据结构,而是直接基于**数组**,通过数组来**标记**每个 Subpage 内存块是否已经分配。如下图所示:[![PoolSubpage](44-Netty 源码解析-Buffer 之 JemallocPoolSubpage.assets/01.png)](http://static.iocoder.cn/images/Netty/2018_09_07/01.png)PoolSubpage
- 一个 Page ,切分出的**多个** Subpage 内存块**大小均等**。
- 每个 Page 拆分的 Subpage 内存块
可以不同
,以 Page 第一次拆分为 Subpage 内存块时请求分配的内存大小为准。例如:
- 初始时,申请一个 16B 的内存块,那么 Page0 被拆成成 512( `8KB / 16B` )个 Subpage 块,使用第 0 块。
- 然后,申请一个 32B 的内存块,那么 Page1 被拆分成 256( `8KB / 32B` )个 Subpage 块,使用第 0 块。
- 最后,申请一个 16B 的内存块,那么重用 Page0 ,使用第 1 块。
- 总结来说,申请 Subpage 内存块时,先去找**大小匹配**,且有可分配 Subpage 内存块的 Page 1如果有则使用其中的一块 Subpage 2如果没有则选择一个新的 Page 拆分成多个 Subpage 内存块,使用第 0 块 Subpage 。
- Subpage 的内存规格,分成 Tiny 和 Small 两类,并且每类有多种大小,如下图所示:[![Subpage 内存规格](44-Netty 源码解析-Buffer 之 JemallocPoolSubpage.assets/02.png)](http://static.iocoder.cn/images/Netty/2018_09_07/02.png)Subpage 内存规格
- 为了方便描述,下文我们会继续将 `ele` 小块描述成“Subpage 内存块”简称“Subpage” 。
# 2. PoolSubpage
`io.netty.buffer.PoolSubpage` ,实现 PoolSubpageMetric 接口Netty 对 Jemalloc Subpage 的实现类。
虽然PoolSubpage 类的命名是“Subpage”实际描述的是Page 切分为**多个** Subpage 内存块的分配情况。那么为什么不直接叫 PoolPage 呢?在 [《精尽 Netty 源码解析 —— Buffer 之 JemallocPoolChunk》](http://svip.iocoder.cn/Netty/ByteBuf-3-2-Jemalloc-chunk) 一文中,我们可以看到,当申请分配的内存规格为 Normal 和 Huge 时,使用的是一块或多块 Page 内存块。如果 PoolSubpage 命名成 PoolPage 后,和这块的分配策略是有所冲突的。或者说,**Subpage ,只是 Page 分配内存的一种形式**。
## 2.1 构造方法
```
/**
* 所属 PoolChunk 对象
*/
final PoolChunk<T> chunk;
/**
* 在 {@link PoolChunk#memoryMap} 的节点编号
*/
private final int memoryMapIdx;
/**
* 在 Chunk 中,偏移字节量
*
* @see PoolChunk#runOffset(int)
*/
private final int runOffset;
/**
* Page 大小 {@link PoolChunk#pageSize}
*/
private final int pageSize;
/**
* Subpage 分配信息数组
*
* 每个 long 的 bits 位代表一个 Subpage 是否分配。
* 因为 PoolSubpage 可能会超过 64 个( long 的 bits 位数 ),所以使用数组。
* 例如Page 默认大小为 8KB Subpage 默认最小为 16 B ,所以一个 Page 最多可包含 8 * 1024 / 16 = 512 个 Subpage 。
* 因此bitmap 数组大小为 512 / 64 = 8 。
* 另外bitmap 的数组大小,使用 {@link #bitmapLength} 来标记。或者说bitmap 数组,默认按照 Subpage 的大小为 16B 来初始化。
* 为什么是这样的设定呢?因为 PoolSubpage 可重用,通过 {@link #init(PoolSubpage, int)} 进行重新初始化。
*/
private final long[] bitmap;
/**
* 双向链表,前一个 PoolSubpage 对象
*/
PoolSubpage<T> prev;
/**
* 双向链表,后一个 PoolSubpage 对象
*/
PoolSubpage<T> next;
/**
* 是否未销毁
*/
boolean doNotDestroy;
/**
* 每个 Subpage 的占用内存大小
*/
int elemSize;
/**
* 总共 Subpage 的数量
*/
private int maxNumElems;
/**
* {@link #bitmap} 长度
*/
private int bitmapLength;
/**
* 下一个可分配 Subpage 的数组位置
*/
private int nextAvail;
/**
* 剩余可用 Subpage 的数量
*/
private int numAvail;
1: // 【构造方法 1】 双向链表,头节点
2: /** Special constructor that creates a linked list head */
3: PoolSubpage(int pageSize) {
4: chunk = null;
5: memoryMapIdx = -1;
6: runOffset = -1;
7: elemSize = -1;
8: this.pageSize = pageSize;
9: bitmap = null;
10: }
11:
12: // 【构造方法 2】 双向链表Page 节点
13: PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
14: this.chunk = chunk;
15: this.memoryMapIdx = memoryMapIdx;
16: this.runOffset = runOffset;
17: this.pageSize = pageSize;
18: // 创建 bitmap 数组
19: bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
20: // 初始化
21: init(head, elemSize);
22: }
```
- Chunk 相关
- `chunk` 属性,所属 PoolChunk 对象。
- `memoryMapIdx` 属性,在 `PoolChunk.memoryMap` 的节点编号,例如节点编号 2048 。
- `runOffset` 属性,在 Chunk 中,偏移字节量,通过 `PoolChunk#runOffset(id)` 方法计算。在 PoolSubpage 中,无相关的逻辑,仅用于 `#toString()` 方法,打印信息。
- `pageSize` 属性Page 大小。
- Subpage 相关
- ```
bitmap
```
属性Subpage
分配信息
数组。
- 1、每个 `long` 的 bits 位代表一个 Subpage 是否分配。因为 PoolSubpage 可能会超过 64 个( `long` 的 bits 位数 )所以使用数组。例如Page 默认大小为 `8KB` Subpage 默认最小为 `16B` ,所以一个 Page 最多可包含 `8 * 1024 / 16` = 512 个 Subpage 。
- 2、在【第 19 行】的代码,创建
```
bitmap
```
数组。我们可以看到,
```
bitmap
```
数组的大小为 8(
```
pageSize >>> 10 = pageSize / 16 / 64 = 512 / 64
```
) 个。
- 为什么是**固定大小**呢?因为 PoolSubpage **可重用**,通过 `#init(PoolSubpage, int)` 进行重新初始化。
- 那么数组大小怎么获得?通过 `bitmapLength` 属性来标记**真正**使用的数组大小。
- `bitmapLength` 属性,`bitmap` 数组的**真正**使用的数组大小。
- `elemSize` 属性,每个 Subpage 的占用内存大小,例如 `16B`、`32B` 等等。
- `maxNumElems` 属性,总共 Subpage 的数量。例如 `16B` 为 512 个,`32b` 为 256 个。
- `numAvail` 属性,剩余可用 Subpage 的数量。
- `nextAvail` 属性,下一个可分配 Subpage 的数组( `bitmap` )位置。可能会有胖友有疑问,`bitmap` 又是数组,又考虑 bits 位,怎么计算位置呢?在 [「2.6 getNextAvail」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 见分晓。
- `doNotDestroy` 属性,是否未销毁。详细解析,见 [「2.5 free」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 中。
- Arena 相关
- `prev` 属性,双向链表,前一个 PoolSubpage 对象。
- `next` 属性,双向链表,后一个 PoolSubpage 对象。
- 详细解析,见 [「2.3 双向链表」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 。
- 构造方法 **1** ,用于创建双向链表的头( head )节点。
- 构造方法
2
,用于创建双向链表的 Page 节点。
- 第 21 行:调用 `#init(PoolSubpage<T> head, int elemSize)` 方法,初始化。详细解析,见 [「2.2 init」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 。
## 2.2 init
`#init(PoolSubpage<T> head, int elemSize)` 方法,初始化。代码如下:
```
1: void init(PoolSubpage<T> head, int elemSize) {
2: // 未销毁
3: doNotDestroy = true;
4: // 初始化 elemSize
5: this.elemSize = elemSize;
6: if (elemSize != 0) {
7: // 初始化 maxNumElems
8: maxNumElems = numAvail = pageSize / elemSize;
9: // 初始化 nextAvail
10: nextAvail = 0;
11: // 计算 bitmapLength 的大小
12: bitmapLength = maxNumElems >>> 6;
13: if ((maxNumElems & 63) != 0) { // 未整除,补 1.
14: bitmapLength ++;
15: }
16:
17: // 初始化 bitmap
18: for (int i = 0; i < bitmapLength; i ++) {
19: bitmap[i] = 0;
20: }
21: }
22: // 添加到 Arena 的双向链表中。
23: addToPool(head);
24: }
```
- 第 3 行:未销毁。
- 第 5 行:初始化
```
elemSize
```
- 第 8 行:初始化 `maxNumElems` 。
- 第 10 行:初始化 `nextAvail` 。
- 第 11 至 15 行:初始化
```
bitmapLength
```
- 第 17 至 20 行:初始化 `bitmap` 。
- 第 23 行:调用 `#addToPool(PoolSubpage<T> head)` 方法中,添加到 Arena 的双向链表中。详细解析,见 [「2.3.1 addToPool」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 中。
## 2.3 双向链表
在每个 Arena 中,有 `tinySubpagePools` 和 `smallSubpagePools` 属性,分别表示 **tiny** 和 **small** 类型的 PoolSubpage 数组。代码如下:
```
// PoolArena.java
/**
* tiny 类型的 PoolSubpage 数组
*
* 数组的每个元素,都是双向链表
*/
private final PoolSubpage<T>[] tinySubpagePools;
/**
* small 类型的 SubpagePools 数组
*
* 数组的每个元素,都是双向链表
*/
private final PoolSubpage<T>[] smallSubpagePools;
```
- 数组的每个元素,通过 `prev` 和 `next` 属性,形成**双向**链表。并且,每个元素,表示对应的 Subpage 内存规格的**双向**链表,例如:`tinySubpagePools[0]` 表示 `16B` `tinySubpagePools[1]` 表示 `32B` 。
- 通过 `tinySubpagePools` 和 `smallSubpagePools` 属性,可以从中查找,是否已经有符合分配内存规格的 Subpage 节点可分配。
- 初始时,每个双向链表,会创建对应的 `head` 节点,代码如下:
```
// PoolArena.java
private PoolSubpage<T> newSubpagePoolHead(int pageSize) {
PoolSubpage<T> head = new PoolSubpage<T>(pageSize);
head.prev = head;
head.next = head;
return head;
}
```
- 比较神奇的是,`head` 的上下节点都是**自己**。也就说,这是个双向环形( 循环 )链表。
### 2.3.1 addToPool
`#addToPool(PoolSubpage<T> head)` 方法中,添加到 Arena 的双向链表中。代码如下:
```
private void addToPool(PoolSubpage<T> head) {
assert prev == null && next == null;
// 将当前节点,插入到 head 和 head.next 中间
prev = head;
next = head.next;
next.prev = this;
head.next = this;
}
```
- 将当前节点,插入到
```
head
```
```
head.next
```
中间。如下图所示:
![插入过程](44-Netty 源码解析-Buffer 之 JemallocPoolSubpage.assets/03.png)
插入过程
- 注意,是在 `head` 和 `head.next` **中间**插入节点噢。
### 2.3.2 removeFromPool
`#removeFromPool()` 方法中,从双向链表中移除。代码如下:
```
private void removeFromPool() {
assert prev != null && next != null;
// 前后节点,互相指向
prev.next = next;
next.prev = prev;
// 当前节点,置空
next = null;
prev = null;
}
```
## 2.4 allocate
`#allocate()` 方法,分配一个 Subpage 内存块,并返回该内存块的位置 `handle` 。代码如下:
> 关于 `handle` 怎么翻译和解释好呢?笔者暂时没想好,官方的定义是 `"Returns the bitmap index of the subpage allocation."` 。
```
1: long allocate() {
2: // 防御性编程,不存在这种情况。
3: if (elemSize == 0) {
4: return toHandle(0);
5: }
6:
7: // 可用数量为 0 ,或者已销毁,返回 -1 ,即不可分配。
8: if (numAvail == 0 || !doNotDestroy) {
9: return -1;
10: }
11:
12: // 获得下一个可用的 Subpage 在 bitmap 中的总体位置
13: final int bitmapIdx = getNextAvail();
14: // 获得下一个可用的 Subpage 在 bitmap 中数组的位置
15: int q = bitmapIdx >>> 6;
16: // 获得下一个可用的 Subpage 在 bitmap 中数组的位置的第几 bits
17: int r = bitmapIdx & 63;
18: assert (bitmap[q] >>> r & 1) == 0;
19: // 修改 Subpage 在 bitmap 中不可分配。
20: bitmap[q] |= 1L << r;
21:
22: // 可用 Subpage 内存块的计数减一
23: if (-- numAvail == 0) { // 无可用 Subpage 内存块
24: // 从双向链表中移除
25: removeFromPool();
26: }
27:
28: // 计算 handle
29: return toHandle(bitmapIdx);
30: }
```
- 第 2 至 5 行:防御性编程,不存在这种情况。
- 第 7 至 10 行:可用数量为 0 ,或者已销毁,返回 -1 ,即**不可分配**。
- 第 12 至 20 行:分配一个 Subpage 内存块。
- 第 13 行:调用 `#getNextAvail()` 方法,获得下一个可用的 Subpage 在 bitmap 中的**总体**位置。详细解析,见 [「2.6 getNextAvail」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 。
- 第 15 行:`bitmapIdx >>> 6 = bitmapIdx / 64` 操作,获得下一个可用的 Subpage 在 bitmap 中**数组的位置**。
- 第 17 行:`bitmapIdx & 63 = bitmapIdx % 64` 操作, 获得下一个可用的 Subpage 在 bitmap 中数组的位置的**第几 bit** 。
- 第 20 行:`| (1L << r)` 操作,修改 Subpage 在 bitmap 中不可分配。
- 第 23 行:可用 Subpage 内存块的计数减一。
- 第 25 行:当 `numAvail == 0` 时,表示无可用 Subpage 内存块。所以,调用 `#removeFromPool()` 方法,从双向链表中移除。详细解析,见 [「2.3.2 removeFromPool」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 。
- 第 29 行:调用 `#toHandle(bitmapIdx)` 方法,计算 `handle` 值。代码如下:
```
private long toHandle(int bitmapIdx) {
return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
}
```
- 低 32 bits `memoryMapIdx` ,可以判断所属 Chunk 的哪个 Page 节点,即 `memoryMap[memoryMapIdx]` 。
- 高 32 bits
```
bitmapIdx
```
,可以判断 Page 节点中的哪个 Subpage 的内存块,即
```
bitmap[bitmapIdx]
```
- 那么为什么会有
```
0x4000000000000000L
```
呢?因为在
```
PoolChunk#allocate(int normCapacity)
```
中:
- 如果分配的是 Page 内存块,返回的是 `memoryMapIdx` 。
- 如果分配的是 Subpage 内存块,返回的是 `handle` 。**但但但是**,如果说 `bitmapIdx = 0` ,那么没有 `0x4000000000000000L` 情况下,就会和【分配 Page 内存块】冲突。因此,需要有 `0x4000000000000000L` 。
- 因为有了 `0x4000000000000000L`(最高两位为 `01` ,其它位为 `0` ),所以获取 `bitmapIdx` 时,通过 `handle >>> 32 & 0x3FFFFFFF` 操作。使用 `0x3FFFFFFF`( 最高两位为 `00` ,其它位为 `1` ) 进行消除 `0x4000000000000000L` 带来的影响。
## 2.5 free
`#free(PoolSubpage<T> head, int bitmapIdx)` 方法,释放指定位置的 Subpage 内存块,并返回当前 Page **是否正在使用中**( `true` )。代码如下:
```
1: boolean free(PoolSubpage<T> head, int bitmapIdx) {
2: // 防御性编程,不存在这种情况。
3: if (elemSize == 0) {
4: return true;
5: }
6: // 获得 Subpage 在 bitmap 中数组的位置
7: int q = bitmapIdx >>> 6;
8: // 获得 Subpage 在 bitmap 中数组的位置的第几 bits
9: int r = bitmapIdx & 63;
10: assert (bitmap[q] >>> r & 1) != 0;
11: // 修改 Subpage 在 bitmap 中可分配。
12: bitmap[q] ^= 1L << r;
13:
14: // 设置下一个可用为当前 Subpage
15: setNextAvail(bitmapIdx);
16:
17: // 可用 Subpage 内存块的计数加一
18: if (numAvail ++ == 0) {
19: // 添加到 Arena 的双向链表中。
20: addToPool(head);
21: return true;
22: }
23:
24: // 还有 Subpage 在使用
25: if (numAvail != maxNumElems) {
26: return true;
27: // 没有 Subpage 在使用
28: } else {
29: // 双向链表中,只有该节点,不进行移除
30: // Subpage not in use (numAvail == maxNumElems)
31: if (prev == next) {
32: // Do not remove if this subpage is the only one left in the pool.
33: return true;
34: }
35:
36: // 标记为已销毁
37: // Remove this subpage from the pool if there are other subpages left in the pool.
38: doNotDestroy = false;
39: // 从双向链表中移除
40: removeFromPool();
41: return false;
42: }
43: }
```
- 第 2 至 5 行:防御性编程,不存在这种情况。
- 第 6 至 12 行:释放指定位置的 Subpage 内存块。
- 第 7 行:`bitmapIdx >>> 6 = bitmapIdx / 64` 操作,获得下一个可用的 Subpage 在 bitmap 中**数组的位置**。
- 第 9 行:`bitmapIdx & 63 = bitmapIdx % 64` 操作, 获得下一个可用的 Subpage 在 bitmap 中数组的位置的**第几 bit** 。
- 第 12 行:`^ (1L << r)` 操作,修改 Subpage 在 bitmap 中可分配。
- 第 15 行:调用 `#setNextAvail(int bitmapIdx)` 方法,设置下一个可用为当前 Subpage 的位置。这样,就能避免下次分配 Subpage 时,再去找位置。代码如下:
```
private void setNextAvail(int bitmapIdx) {
nextAvail = bitmapIdx;
}
```
- 第 18 行:可用 Subpage 内存块的计数加一。
- 第 20 行:当之前 `numAvail == 0` 时,表示**又有**可用 Subpage 内存块。所以,调用 `#addToPool(PoolSubpage<T> head)` 方法,添加到 Arena 的双向链表中。详细解析,见 [「2.3.1 addToPool」](https://svip.iocoder.cn/Netty/ByteBuf-3-3-Jemalloc-subpage/#) 。
- 第 21 行:返回 `true` ,正在使用中。
- 第 24 至 26 行:返回 `true` ,因为还有其它在使用的 Subpage 内存块。
- 第 27 至 42 行:没有 Subpage 在使用。
- 第 29 至 34 行:返回 `true` ,因为通过 `prev == next` 可判断,当前节点为双向链表中的唯一节点,不进行移除。也就说,该节点后续,继续使用。
- 第 36 至 41 行:返回
```
false
```
,不在使用中。
- 第 38 行:标记为已销毁。
- 第 40 行:调用 `#removeFromPool()` 方法,从双向链表中移除。因为此时双向链表中,还有其它节点可使用,**没必要保持多个相同规格的节点**。
------
关于为什么 `#free(PoolSubpage<T> head, int bitmapIdx)` 方法,需要返回 `true` 或 `false` 呢?胖友再看看 `PoolChunk#free(long handle)` 方法,就能明白。答案是,如果不再使用,可以将该节点( Page )从 Chunk 中释放,标记为可用。😈😈😈
## 2.6 getNextAvail
`#getNextAvail()` 方法,获得下一个可用的 Subpage 在 bitmap 中的**总体**位置。代码如下:
```
private int getNextAvail() {
int nextAvail = this.nextAvail;
// <1> nextAvail 大于 0 ,意味着已经“缓存”好下一个可用的位置,直接返回即可。
if (nextAvail >= 0) {
this.nextAvail = -1;
return nextAvail;
}
// <2> 寻找下一个 nextAvail
return findNextAvail();
}
```
- ```
<1>
```
处,如果
```
nextAvail
```
大于 0 ,意味着已经“缓存”好下一个可用的位置,直接返回即可。
- 获取好后,会将 `nextAvail` 置为 -1 。意味着,下次需要寻找下一个 `nextAvail` 。
- `<2>` 处,调用 `#findNextAvail()` 方法,寻找下一个 `nextAvail` 。代码如下:
```
private int findNextAvail() {
final long[] bitmap = this.bitmap;
final int bitmapLength = this.bitmapLength;
// 循环 bitmap
for (int i = 0; i < bitmapLength; i ++) {
long bits = bitmap[i];
// ~ 操作,如果不等于 0 ,说明有可用的 Subpage
if (~bits != 0) {
// 在这 bits 寻找可用 nextAvail
return findNextAvail0(i, bits);
}
}
// 未找到
return -1;
}
```
- 代码比较简单,胖友直接看注释。
- 调用 `#findNextAvail0(int i, long bits)` 方法,在这 bits 寻找可用 `nextAvail` 。代码如下:
```
1: private int findNextAvail0(int i, long bits) {
2: final int maxNumElems = this.maxNumElems;
3: // 计算基础值,表示在 bitmap 的数组下标
4: final int baseVal = i << 6; // 相当于 * 64
5:
6: // 遍历 64 bits
7: for (int j = 0; j < 64; j ++) {
8: // 计算当前 bit 是否未分配
9: if ((bits & 1) == 0) {
10: // 可能 bitmap 最后一个元素,并没有 64 位,通过 baseVal | j < maxNumElems 来保证不超过上限。
11: int val = baseVal | j;
12: if (val < maxNumElems) {
13: return val;
14: } else {
15: break;
16: }
17: }
18: // 去掉当前 bit
19: bits >>>= 1;
20: }
21:
22: // 未找到
23: return -1;
24: }
```
- 第 4 行:计算基础值,表示在 `bitmap` 的数组**下标**。通过 `i << 6 = i * 64` 的计算,我们可以通过 `i >>> 6 = i / 64` 的方式,知道是 `bitmap` 数组的第几个元素。
- 第 7 行:循环 64 bits 。
- 第 9 行:
```
(bits & 1) == 0
```
操作,计算当前 bit 是否
未分配
- 第 11 行:`baseVal | j` 操作,使用**低 64 bits** ,表示分配 `bitmap` 数组的元素的**第几 bit** 。
- 第 12 行:可能
```
bitmap
```
数组的最后一个元素,并没有 64 位,通过
```
baseVal | j < maxNumElems
```
来保证不超过上限。如果
- 第 13 行:未超过,返回 `val` 。
- 第 15 行:超过,结束循环,最终返回 `-1` 。
- 第 19 行:去掉当前 bit 。这样,下次循环就可以判断下一个 bit 是否**未分配**。
- 第 23 行:返回 `-1` ,表示未找到。
## 2.6 destroy
`#destroy()` 方法,销毁。代码如下:
```
void destroy() {
if (chunk != null) {
chunk.destroy();
}
}
```
## 2.7 PoolSubpageMetric
`io.netty.buffer.PoolSubpageMetric` PoolSubpage Metric 接口。代码如下:
```
public interface PoolSubpageMetric {
/**
* Return the number of maximal elements that can be allocated out of the sub-page.
*/
int maxNumElements();
/**
* Return the number of available elements to be allocated.
*/
int numAvailable();
/**
* Return the size (in bytes) of the elements that will be allocated.
*/
int elementSize();
/**
* Return the size (in bytes) of this page.
*/
int pageSize();
}
```
------
PoolChunk 对 PoolChunkMetric 接口的实现,代码如下:
```
@Override
public int maxNumElements() {
synchronized (chunk.arena) {
return maxNumElems;
}
}
@Override
public int numAvailable() {
synchronized (chunk.arena) {
return numAvail;
}
}
@Override
public int elementSize() {
synchronized (chunk.arena) {
return elemSize;
}
}
@Override
public int pageSize() {
return pageSize;
}
```
# 666. 彩蛋
PoolSubpage 相比 PoolChunk 来说,简单好多。嘿嘿。
参考如下文章:
- 占小狼 [《深入浅出Netty内存管理 PoolSubpage》](https://www.jianshu.com/p/d91060311437)
- Hypercube [《自顶向下深入分析NettyPoolSubpage》](https://www.jianshu.com/p/7afd3a801b15)