netty5学习笔记-内存池2-PoolSubpage
2018-03-27 14:14
253 查看
转载于:https://blog.csdn.net/youaremoon/article/details/47984409前面我们讲过了负责内存分配的类PoolChunk,它最小的分配单位为page, 而默认的page size为8K。在实际的应用中,会存在很多小块内存的分配,如果小块内存也占用一个page明显很浪费,针对这种情况,可以将8K的page拆成更小的块,这已经超出chunk的管理范围了,这个时候就出现了PoolSubpage, 其实PoolSubpage做的事情和PoolChunk做的事情类似,只是PoolSubpage管理的是更小的一段内存。
如上图,PoolSubpage将chunk中的一个page再次划分,分成相同大小的N份,这里暂且叫Element,通过对每一个Element的标记与清理标记来进行内存的分配与释放。[java] view plain copyfinal class PoolSubpage<T> {
private final int memoryMapIdx; // 当前page在chunk中的id
private final int runOffset; // 当前page在chunk.memory的偏移量
private final int pageSize; // page大小
private final long[] bitmap; // 这个bitmap的实现和BitSet相同,通过对每一个二进制位的标记来修改一段内存的占用状态
PoolSubpage<T> prev; // 前一个节点,这里要配合PoolArena看,后面再说
PoolSubpage<T> next;
boolean doNotDestroy; // 表示该page在使用中,不能被清除
int elemSize; // 该page切分后每一段的大小
private int maxNumElems; // 该page包含的段数量
private int bitmapLength; // bitmap需要用到的长度
private int nextAvail; // 下一个可用的位置
private int numAvail; // 可用的段数量
PoolSubpage(PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
this.chunk = chunk;
this.memoryMapIdx = memoryMapIdx;
this.runOffset = runOffset;
this.pageSize = pageSize;
// 这里为什么是16,64两个数字呢,elemSize是经过normCapacity处理的数字,最小值为16;
// 所以一个page最多可能被分成pageSize/16段内存,而一个long可以表示64个内存段的状态;
// 因此最多需要pageSize/16/64个元素就能保证所有段的状态都可以管理
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
init(elemSize);
}
// 这个方法有两种情况下会调用
// 1、类初始化时
// 2、整个subpage被回收后重新分配
void init(int elemSize) {
doNotDestroy = true;
this.elemSize = elemSize;
if (elemSize != 0) {
maxNumElems = numAvail = pageSize / elemSize;
nextAvail = 0;
bitmapLength = maxNumElems >>> 6;
if ((maxNumElems & 63) != 0) {
bitmapLength ++;
}
// 用来表示段状态的值全部需要被清零
for (int i = 0; i < bitmapLength; i ++) {
bitmap[i] = 0;
}
}
addToPool();
}
// chunk在分配page时,如果是8K以下的段则交给subpage管理,然而chunk并没有将subpage暴露给外部,subpage只好自谋生路,
// 在初始化或重新分配时将自己加入到chunk.arena的pool中,通过arena进行后续的管理(包括复用subpage上的其他element,arena目前还没讲到,后面会再提到)
private void addToPool() {
PoolSubpage<T> head = chunk.arena.findSubpagePoolHead(elemSize);
assert prev == null && next == null;
prev = head;
next = head.next;
next.prev = this;
head.next = this;
}
下面看看subpage是如何进行内部的内存分配的:[java] view plain copy // 分配一个可用的element并标记
long allocate() {
if (elemSize == 0) {
return toHandle(0);
}
// 没有可用的内存或者已经被销毁
if (numAvail == 0 || !doNotDestroy) {
return -1;
}
// 找到当前page中分配的段的index
final int bitmapIdx = getNextAvail();
// 算出对应index的标志位在数组中的位置q
int q = bitmapIdx >>> 6;
// 将>=64的那一部分二进制抹掉得到一个小于64的数
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) == 0;
// 对应位置值设置为1表示当前element已经被分配, 这几句看起来很郁闷,转换成我们常见的BitSet,其实就是bitSet.set(q, true)
bitmap[q] |= 1L << r;
// 如果当前page没有可用的内存则从arena的pool中移除
if (-- numAvail == 0) {
removeFromPool();
}
return toHandle(bitmapIdx);
}
// 释放指定element
boolean free(int bitmapIdx) {
if (elemSize == 0) {
return true;
}
// 下面这几句转换成我们常见的BitSet,其实就是bitSet.set(q, false)
int q = bitmapIdx >>> 6;
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) != 0;
bitmap[q] ^= 1L << r;
// 将这个index设置为可用, 下次分配时会直接分配这个位置的内存
setNextAvail(bitmapIdx);
// numAvail=0说明之前已经从arena的pool中移除了,现在变回可用,则再次交给arena管理
if (numAvail ++ == 0) {
addToPool();
return true;
}
if (numAvail != maxNumElems) {
return true;
} else {
// 注意这里的特殊处理,如果arena的pool中没有可用的subpage,则保留,否则将其从pool中移除。
// 这样尽可能的保证arena分配小内存时能直接从pool中取,而不用再到chunk中去获取。
// Subpage not in use (numAvail == maxNumElems)
if (prev == next) {
// Do not remove if this subpage is the only one left in the pool.
return true;
}
// Remove this subpage from the pool if there are other subpages left in the pool.
doNotDestroy = false;
removeFromPool();
return false;
}
}
前面的代码忽略了查找可用element的index的分析,虽然很简单,还是忍不住贴出来,省得自己后面记不清了。[java] view plain copyprivate int getNextAvail() {
// nextAvail>=0时,表示明确的知道这个element未被分配,此时直接返回就可以了
// >=0 有两种情况:1、刚初始化;2、有element被释放且还未被分配
// 每次分配完成nextAvail就被置为-1,因为这个时候除非计算一次,否则无法知道下一个可用位置在哪
int nextAvail = this.nextAvail;
if (nextAvail >= 0) {
this.nextAvail = -1;
return nextAvail;
}
return findNextAvail();
}
private int findNextAvail() {
// 没有明确的可用位置时则挨个查找
final long[] bitmap = this.bitmap;
final int bitmapLength = this.bitmapLength;
for (int i = 0; i < bitmapLength; i ++) {
long bits = bitmap[i];
// 说明这个位置段中还有可以分配的element
if (~bits != 0) {
return findNextAvail0(i, bits);
}
}
return -1;
}
private int findNextAvail0(int i, long bits) {
final int maxNumElems = this.maxNumElems;
final int baseVal = i << 6;
for (int j = 0; j < 64; j ++) {
// 如果该位置的值为0,表示还未分配
if ((bits & 1) == 0) {
int val = baseVal | j;
if (val < maxNumElems) {
return val;
} else {
break;
}
}
bits >>>= 1;
}
return -1;
}
下面我们再总结下,前文讲到chunk进行内存分配时会将小块内存(<8K)交给PageSubpage管理,PageSubpage将一个完整的page再次细分成多个element。这里带来了一个新问题,chunk并没有暴露出PageSubpage,所以PageSubpage只好自寻出路,越过chunk将自己交给chunk的管理者arena来管理。其实并不是chunk不想管理subpage, 而是通过将subpage前置到arena中,小内存的分配性能更高。而这里说到的arena是内存分配的一个更高的管理者, 好吧,填了一个坑又挖出来一个坑,到底什么时候才能了解内存池的全貌!?不要急,先留下几个问题,让我们带着问题来进行后面的分析: 1、 chunk管理着一段大小为64M(默认情况下)的内存,如果超过64M应该如何处理,是不是需要一个类来管理多个chunk? 前面提到的arena是这个管理类吗?(虽然前面确实是有chunk.arena可以供我们大胆猜测) 2、Poolsubpage将自己交给arena管理,但subpage的element size存在很多可能的值,且其内部可能会被瓜分得七零八落的,arena是如何高效的处理,使其可以更快的定位到可分配足够内存的subpage的?
如上图,PoolSubpage将chunk中的一个page再次划分,分成相同大小的N份,这里暂且叫Element,通过对每一个Element的标记与清理标记来进行内存的分配与释放。[java] view plain copyfinal class PoolSubpage<T> {
private final int memoryMapIdx; // 当前page在chunk中的id
private final int runOffset; // 当前page在chunk.memory的偏移量
private final int pageSize; // page大小
private final long[] bitmap; // 这个bitmap的实现和BitSet相同,通过对每一个二进制位的标记来修改一段内存的占用状态
PoolSubpage<T> prev; // 前一个节点,这里要配合PoolArena看,后面再说
PoolSubpage<T> next;
boolean doNotDestroy; // 表示该page在使用中,不能被清除
int elemSize; // 该page切分后每一段的大小
private int maxNumElems; // 该page包含的段数量
private int bitmapLength; // bitmap需要用到的长度
private int nextAvail; // 下一个可用的位置
private int numAvail; // 可用的段数量
PoolSubpage(PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
this.chunk = chunk;
this.memoryMapIdx = memoryMapIdx;
this.runOffset = runOffset;
this.pageSize = pageSize;
// 这里为什么是16,64两个数字呢,elemSize是经过normCapacity处理的数字,最小值为16;
// 所以一个page最多可能被分成pageSize/16段内存,而一个long可以表示64个内存段的状态;
// 因此最多需要pageSize/16/64个元素就能保证所有段的状态都可以管理
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
init(elemSize);
}
// 这个方法有两种情况下会调用
// 1、类初始化时
// 2、整个subpage被回收后重新分配
void init(int elemSize) {
doNotDestroy = true;
this.elemSize = elemSize;
if (elemSize != 0) {
maxNumElems = numAvail = pageSize / elemSize;
nextAvail = 0;
bitmapLength = maxNumElems >>> 6;
if ((maxNumElems & 63) != 0) {
bitmapLength ++;
}
// 用来表示段状态的值全部需要被清零
for (int i = 0; i < bitmapLength; i ++) {
bitmap[i] = 0;
}
}
addToPool();
}
// chunk在分配page时,如果是8K以下的段则交给subpage管理,然而chunk并没有将subpage暴露给外部,subpage只好自谋生路,
// 在初始化或重新分配时将自己加入到chunk.arena的pool中,通过arena进行后续的管理(包括复用subpage上的其他element,arena目前还没讲到,后面会再提到)
private void addToPool() {
PoolSubpage<T> head = chunk.arena.findSubpagePoolHead(elemSize);
assert prev == null && next == null;
prev = head;
next = head.next;
next.prev = this;
head.next = this;
}
下面看看subpage是如何进行内部的内存分配的:[java] view plain copy // 分配一个可用的element并标记
long allocate() {
if (elemSize == 0) {
return toHandle(0);
}
// 没有可用的内存或者已经被销毁
if (numAvail == 0 || !doNotDestroy) {
return -1;
}
// 找到当前page中分配的段的index
final int bitmapIdx = getNextAvail();
// 算出对应index的标志位在数组中的位置q
int q = bitmapIdx >>> 6;
// 将>=64的那一部分二进制抹掉得到一个小于64的数
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) == 0;
// 对应位置值设置为1表示当前element已经被分配, 这几句看起来很郁闷,转换成我们常见的BitSet,其实就是bitSet.set(q, true)
bitmap[q] |= 1L << r;
// 如果当前page没有可用的内存则从arena的pool中移除
if (-- numAvail == 0) {
removeFromPool();
}
return toHandle(bitmapIdx);
}
// 释放指定element
boolean free(int bitmapIdx) {
if (elemSize == 0) {
return true;
}
// 下面这几句转换成我们常见的BitSet,其实就是bitSet.set(q, false)
int q = bitmapIdx >>> 6;
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) != 0;
bitmap[q] ^= 1L << r;
// 将这个index设置为可用, 下次分配时会直接分配这个位置的内存
setNextAvail(bitmapIdx);
// numAvail=0说明之前已经从arena的pool中移除了,现在变回可用,则再次交给arena管理
if (numAvail ++ == 0) {
addToPool();
return true;
}
if (numAvail != maxNumElems) {
return true;
} else {
// 注意这里的特殊处理,如果arena的pool中没有可用的subpage,则保留,否则将其从pool中移除。
// 这样尽可能的保证arena分配小内存时能直接从pool中取,而不用再到chunk中去获取。
// Subpage not in use (numAvail == maxNumElems)
if (prev == next) {
// Do not remove if this subpage is the only one left in the pool.
return true;
}
// Remove this subpage from the pool if there are other subpages left in the pool.
doNotDestroy = false;
removeFromPool();
return false;
}
}
前面的代码忽略了查找可用element的index的分析,虽然很简单,还是忍不住贴出来,省得自己后面记不清了。[java] view plain copyprivate int getNextAvail() {
// nextAvail>=0时,表示明确的知道这个element未被分配,此时直接返回就可以了
// >=0 有两种情况:1、刚初始化;2、有element被释放且还未被分配
// 每次分配完成nextAvail就被置为-1,因为这个时候除非计算一次,否则无法知道下一个可用位置在哪
int nextAvail = this.nextAvail;
if (nextAvail >= 0) {
this.nextAvail = -1;
return nextAvail;
}
return findNextAvail();
}
private int findNextAvail() {
// 没有明确的可用位置时则挨个查找
final long[] bitmap = this.bitmap;
final int bitmapLength = this.bitmapLength;
for (int i = 0; i < bitmapLength; i ++) {
long bits = bitmap[i];
// 说明这个位置段中还有可以分配的element
if (~bits != 0) {
return findNextAvail0(i, bits);
}
}
return -1;
}
private int findNextAvail0(int i, long bits) {
final int maxNumElems = this.maxNumElems;
final int baseVal = i << 6;
for (int j = 0; j < 64; j ++) {
// 如果该位置的值为0,表示还未分配
if ((bits & 1) == 0) {
int val = baseVal | j;
if (val < maxNumElems) {
return val;
} else {
break;
}
}
bits >>>= 1;
}
return -1;
}
下面我们再总结下,前文讲到chunk进行内存分配时会将小块内存(<8K)交给PageSubpage管理,PageSubpage将一个完整的page再次细分成多个element。这里带来了一个新问题,chunk并没有暴露出PageSubpage,所以PageSubpage只好自寻出路,越过chunk将自己交给chunk的管理者arena来管理。其实并不是chunk不想管理subpage, 而是通过将subpage前置到arena中,小内存的分配性能更高。而这里说到的arena是内存分配的一个更高的管理者, 好吧,填了一个坑又挖出来一个坑,到底什么时候才能了解内存池的全貌!?不要急,先留下几个问题,让我们带着问题来进行后面的分析: 1、 chunk管理着一段大小为64M(默认情况下)的内存,如果超过64M应该如何处理,是不是需要一个类来管理多个chunk? 前面提到的arena是这个管理类吗?(虽然前面确实是有chunk.arena可以供我们大胆猜测) 2、Poolsubpage将自己交给arena管理,但subpage的element size存在很多可能的值,且其内部可能会被瓜分得七零八落的,arena是如何高效的处理,使其可以更快的定位到可分配足够内存的subpage的?
相关文章推荐
- netty5学习笔记-内存池2-PoolSubpage
- iOS tabbarviewcontroller 快速点击subviewcontroller崩溃(anonymous namespace)::AutoreleasePoolPage::pop(void
- Netty源码分析:PoolSubpage
- netty5学习笔记-内存池5-PoolThreadCache
- Netty内存池之PoolSubPage
- Netty学习之旅------源码分析Netty内存池分配机制初探--PoolArena、PoolChunk、PoolSubpage等数据结构分析
- netty5学习笔记-内存池3-PoolChunkList
- netty5学习笔记-内存池5-PoolThreadCache
- Nginx源码分析---内存池结构ngx_pool_t及内存管理
- 不定长内存池之apr_pool
- nginx源码分析—内存池结构ngx_pool_t及内存管理
- boost::pool与内存池技术
- boost::pool与内存池技术
- Nginx高效数据结构(5)——内存池(ngx_pool_t)
- netty5学习笔记-内存池4-PoolArena
- 菜鸟nginx源代码剖析数据结构篇(九) 内存池ngx_pool_t
- nginx源码分析—内存池结构ngx_pool_t及内存管理
- C++ 内存池 -- C++ Memory Pool
- boost::pool与内存池技术
- GlusterFS之内存池(mem-pool)使用实例分析