本文只简述Buddy算法在netty内存池中简单实现
- 内存分配的最小单位为16B。
- < 512B的请求为Tiny,< 8KB(PageSize)的请求为Small,<= 16MB(ChunkSize)的请求为Normal,> 16MB(ChunkSize)的请求为Huge。
- < 512B的请求以16B为起点每次增加16B;>= 512B的请求则每次加倍。
- 不在表格中的请求大小,将向上规范化到表格中的数据,比如:请求分配511B、512B、513B,将依次规范化为512B、512B、1KB。
Chunk 和Page
1个Chunk由2048个Page组成,每个Page大小8K,图中最底层表示一个被切分为2048个Page的Chunk块。自底向上,每一层节点作为上一层的子节点构造出一棵满二叉树,然后按层分配满足要求的内存块。以待分配序列8KB、16KB、8KB为例分析分配过程(每个Page大小8KB)
- 8KB–需要一个Page,第11层满足要求,故分配2048节点即Page0;
- 16KB–需要两个Page,故需要在第10层进行分配,而1024的子节点2048已分配,从左到右找到满足要求的1025节点,故分配节点1025即Page2和Page3;
- 8KB–需要一个Page,第11层满足要求,2048已分配,从左到右找到2049节点即Page1进行分配。
分配结束后,已分配连续的Page0-Page3,这样的连续内存块,大大减少内部碎片并提高内存使用率。
netty的PoolChunk维护着一个memoryMap(是一个数组)
int maxOrder = 11;
int maxSubpageAllocs = 1 << maxOrder;
// Generate the memory map.
byte[] memoryMap = new byte[maxSubpageAllocs << 1];
byte[] depthMap = new byte[memoryMap.length];
int memoryMapIndex = 1;
for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
int depth = 1 << d;
for (int p = 0; p < depth; ++ p) {
// in each level traverse left to right and set value to the depth of subtree
memoryMap[memoryMapIndex] = (byte) d;
depthMap[memoryMapIndex] = (byte) d;
memoryMapIndex ++;
}
}
memoryMap数组中每个位置保存的是该节点所在的层数,有什么作用?对于节点512,其层数是9,则:
- 如果memoryMap[512] = 9,则表示其本身到下面所有的子节点都可以被分配;
- 如果memoryMap[512] = 10, 则表示节点512下有子节点已经分配过,则该节点不能直接被分配,而其子节点中的第10层还存在未分配的节点;
- 如果memoryMap[512] = 12 (即总层数 + 1), 可分配的深度已经大于总层数, 则表示该节点下的所有子节点都已经被分配。