PoolArena

在PooledByteBufAllocate初始化最后一步,就是初始化PoolArena。PoolArena是对内存申请和释放的一个抽象。在类层次上,PoolArena有2个子类DirectArena和HeapArena,并且将内存处理的工作都统一抽象到了PoolArena中。PoolArena主要管理的对象是tinySubpagePools、smallSubpagePools和PoolChunkList,前两者在PoolSubpage一节已有提及,而PoolChunkList则是一组链表对象,并且会根据PoolChunk内存使用率的变化将其中的元素进行转移。

PoolArena成员介绍

通过PoolArena的构造函数看一下常用成员变量,这里忽略了metric相关的变量。

protected PoolArena(PooledByteBufAllocator parent, int pageSize,
int maxOrder, int pageShifts, int chunkSize, int cacheAlignment) {
this.parent = parent;
this.pageSize = pageSize;
this.maxOrder = maxOrder;
this.pageShifts = pageShifts;
this.chunkSize = chunkSize;
directMemoryCacheAlignment = cacheAlignment;
directMemoryCacheAlignmentMask = cacheAlignment - 1;
subpageOverflowMask = ~(pageSize - 1);
tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);
for (int i = 0; i < tinySubpagePools.length; i ++) {
tinySubpagePools[i] = newSubpagePoolHead(pageSize);
} numSmallSubpagePools = pageShifts - 9;
smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
for (int i = 0; i < smallSubpagePools.length; i ++) {
smallSubpagePools[i] = newSubpagePoolHead(pageSize);
} q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, chunkSize);
q075 = new PoolChunkList<T>(this, q100, 75, 100, chunkSize);
q050 = new PoolChunkList<T>(this, q075, 50, 100, chunkSize);
q025 = new PoolChunkList<T>(this, q050, 25, 75, chunkSize);
q000 = new PoolChunkList<T>(this, q025, 1, 50, chunkSize);
qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, chunkSize); q100.prevList(q075);
q075.prevList(q050);
q050.prevList(q025);
q025.prevList(q000);
q000.prevList(null);
qInit.prevList(qInit);

构造函数前半段是一些熟悉的属性——pageSize、maxOrder等,忽略掉这些属性设置外,PoolArena主要的成员变量就是tinySubpagePools和smallSubpagePools,这2个在PoolSubpage一小节已经讲述过,这里看一下PoolChunkList。

PoolChunkList

为了理解PoolChunkList,我们先看一下它的构造函数。

PoolChunkList实例化

PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage, int chunkSize) {
this.arena = arena;
this.nextList = nextList;
this.minUsage = minUsage;
this.maxUsage = maxUsage;
maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
} private static int calculateMaxCapacity(int minUsage, int chunkSize) {
minUsage = Math.max(1, minUsage);
if (minUsage == 100) {
return 0;
}
return (int) (chunkSize * (100L - minUsage) / 100L);
}

在构造函数中定义了PoolChunkList中元素的内存最小、最大使用率,并且根据最小占用率计算出最大内存容量。此外由nextList还可以看出来,PoolChunkList除了自身是一条PoolChunk的链表外,不同的PoolChunkList还会形成一个链表。除了上述几个成员变量外,PoolChunkList还有head和prevList,前者指向PoolChunk的最新加入的节点,后者是PoolChunkList的前继节点。

PoolChunkList添加PoolChunk

接着看一下PoolChunkList添加PoolChunk的add方法。add方法在3种情况被调用:

  1. 新创建的Chunk,由一个特殊的PoolChunkList——qInit调用,加入链表中。
  2. 在chunk内存使用率变化时,需要调整其所在链表时调用
  3. 调整链表时,发现当前PoolChunkList不满足内存使用率,递归调用。
void add(PoolChunk<T> chunk) {
if (chunk.usage() >= maxUsage) {
nextList.add(chunk);
return;
}
add0(chunk);
} 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;
}
}

add方法首先计算传入的chunk的使用率,如果使用率超过了当前PoolChunkList的最大使用率,则递归调用add方法,直到寻找到合适的PoolChunkList后,调用add0。add0就是流程化的双向链表头插法添加操作。

PoolChunkList移动PoolChunk

与add相反的是move操作,在free内存或者调整内存使用率时,move0方法被调用,它会根据chunk的使用率将其从高使用率的PoolChunkList移动到低使用率的PoolChunkList中。

private boolean move0(PoolChunk<T> chunk) {
if (prevList == null) {
assert chunk.usage() == 0;
return false;
}
return prevList.move(chunk);
} private boolean move(PoolChunk<T> chunk) {
if (chunk.usage() < minUsage) {
return move0(chunk);
}
add0(chunk);
return true;
}

move0这里首先进行判断,如果当前节点为0,意味着chunk是从q000链表移除,而q000的最小使用率为1%,所以这里做了一个断言chunk使用率为0。之后调用prevList的move方法。

move方法也是先判断使用率是否小于调用者最小使用率,若小于则再次调用move0方法,否则将chunk添加到调用该方法的PoolChunkList中。

PoolChunkList申请内存

boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
if (normCapacity > maxCapacity) {
return false;
}
for (PoolChunk<T> cur = head; cur != null; cur = cur.next) {
if (cur.allocate(buf, reqCapacity, normCapacity)) {
if (cur.usage() >= maxUsage) {
remove(cur);
nextList.add(cur);
}
return true;
}
}
return false;
}

首先注意到有2个capacity的入参,一个是reqCapacity,表示外部请求申请的内存大小,一个是normCapacity,表示经过规整化为2的幂次方的内存大小,也是实际申请的内存大小,所以在与PoolChunkList的maxCapacity判断时用的maxCapacity。

满足内存大小申请条件后,用当前PoolChunkList内部的PoolChunk链表一个个尝试申请。如果在某个PoolChunk申请内存后,发现其内存使用率超过了当前PoolChunkList的最大使用率,则将其转移到下一个PoolChunkList中去。

PoolChunkList初始化

回到PoolArena中,这里初始化了6个PoolChunkList,组成了一个q000 <-> q025 <-> q050 <-> q075 <-> q100的双向链表。qInit比较特殊,他的后继节点是q000,但前继节点是自身。从名字上也可以看出,qInit负责创建PoolChunk,且在PoolChunk使用率超过qInit的maxUsage(25%)后,将其转移到q000链表去。因为q000最小使用率就是1%,再小就没必要留着PoolChunk了,因此q000的前继节点为null。

PoolArena申请内存

外界通过调用allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity)方法来申请内存。

PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
PooledByteBuf<T> buf = newByteBuf(maxCapacity);
allocate(cache, buf, reqCapacity);
return buf;
}

这个方法首先调用了一个抽象方法newByteBuf(int maxCapacity)获取到PooledByteBuf,然后将申请的内存通过的allocate方法放入这个ByteBuf中。

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
final int normCapacity = normalizeCapacity(reqCapacity);
if (isTinyOrSmall(normCapacity)) {
int tableIdx;
PoolSubpage<T>[] table;
boolean tiny = isTiny(normCapacity);
if (tiny) {
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
return;
}
tableIdx = tinyIdx(normCapacity);
table = tinySubpagePools;
} else {
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
return;
}
tableIdx = smallIdx(normCapacity);
table = smallSubpagePools;
}
final PoolSubpage<T> head = table[tableIdx]; synchronized (head) {
final PoolSubpage<T> s = head.next;
if (s != head) {
assert s.doNotDestroy && s.elemSize == normCapacity;
long handle = s.allocate();
assert handle >= 0;
s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity);
incTinySmallAllocation(tiny);
return;
}
}
synchronized (this) {
allocateNormal(buf, reqCapacity, normCapacity);
}
incTinySmallAllocation(tiny);
return;
}
if (normCapacity <= chunkSize) {
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
return;
}
synchronized (this) {
allocateNormal(buf, reqCapacity, normCapacity);
++allocationsNormal;
}
} else {
allocateHuge(buf, reqCapacity);
}
}

可以看出这个方法是相当的长,但总体逻辑还算清晰,惯例先分步骤:

  1. 将申请内存规整化。
  2. 申请tiny、small级别的内存。
  3. 申请normal级别的内存。
  4. 申请huge级别的内存。

将申请内存规整化。

将内存大小规整化这个概念前面多次提到,他的作用是将内存大小向上对齐。对于512B以下大小的内存,对齐到大于申请内存的下一个16B的倍数。比如15B,会对齐到16B,41B会对齐到48B。对于大于512B的内存,对齐到大于申请内存的下一个2的幂次方,比如申请600B,会对齐到1KB,申请3KB,会对齐到4KB。

它的实现大量使用位运算,代码如下。由于directMemoryCacheAlignment默认为0,且通常也不会设置,这里忽略了。

int normalizeCapacity(int reqCapacity) {
checkPositiveOrZero(reqCapacity, "reqCapacity");
if (reqCapacity >= chunkSize) {
return reqCapacity;
}
if (!isTiny(reqCapacity)) {
int normalizedCapacity = reqCapacity;
normalizedCapacity --;
normalizedCapacity |= normalizedCapacity >>> 1;
normalizedCapacity |= normalizedCapacity >>> 2;
normalizedCapacity |= normalizedCapacity >>> 4;
normalizedCapacity |= normalizedCapacity >>> 8;
normalizedCapacity |= normalizedCapacity >>> 16;
normalizedCapacity ++;
if (normalizedCapacity < 0) {
normalizedCapacity >>>= 1;
}
return normalizedCapacity;
} if ((reqCapacity & 15) == 0) {
return reqCapacity;
}
return (reqCapacity & ~15) + 16;
}

首先检验了内存是否不是负数,否则直接抛出异常。之后判断申请内存是否超出了ChunkSize级别,即16MB,若超出这个大小,则达到了huge级别,无需规整化。

若不是tiny级别内存,则先自减,分别与自身无符号右移1、2、4、8、16位做或运算,再自增。完成后还做了是否越界的判断,越界时,无符号右移一位。

比如600B,二进制为0010 0101 1000,自减后变成0010 0101 0111,做完无符号右移后,变成0011 1111 1111,自增后,变成0100 0000 0000,即2^10=1024B。

若为tiny级别,&15==0表明刚好是16的倍数,直接返回。否则对15取反后,与内存做与运算,再加上16。因为15取反后,低4位为0,高位全部为1,相当于掩码。

申请tiny、small级别的内存

在对内存规整化以后,首先需要判断它在哪个范围。对tiny、small级别来说采用的是 (subpageOverflowMask & normCapacity) == 0 这样一个判断条件,其中subpageOverflowMask = ~(pageSize - 1),默认低13位为0,其余高位为1。之后又使用 (normCapacity & 0xFFFFFE00) == 0 进一步划分tiny和small。tiny、small,包括后续的norm,都会先尝试用缓存分配,如果分配成功则直接返回。缓存分配留待后续。

在缓存分配失败后,会去相应的PoolArena中的PoolSubpage数组定位到对应大小PoolSubpage的head结点。

在分配之前,先对head加锁,因为此时PoolChunk.allocateSubpage和PoolChunk.free可能会并发修改PoolSubpage链表。

加锁完成进入临界区,迭代head的下一个非空PoolSubpage节点,调用其allocate方法进行内存分配。

在分配完成后,还需要调用其所在PoolChunk的initBufWithSubpage方法,最终会调用ByteBuf.init方法进行初始化。分配完成后,增加对应的分配计数器

若不存在非空的PoolSubpage节点,则还是需要在normal分配。

申请normal级别的内存

在分配normal级别的内存时,需要对PoolArena对象加锁,防止其他线程同时分配内存。具体分配的代码如下

private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
q075.allocate(buf, reqCapacity, normCapacity)) {
return;
}
// Add a new chunk.
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
boolean success = c.allocate(buf, reqCapacity, normCapacity);
assert success;
qInit.add(c);
}

首先尝试在5个PoolChunkList中分配内存。PoolChunkList分配内存的相关代码在前文已经涉及。它最终会将分配的PoolChunk放在合适使用率的PoolChunkList中。

若PoolChunkList中没有分配成功(比如在初始状态下,PoolChunkList中除了head节点没有其他PoolChunk对象),在会新增一个PoolChunk,调用新增PoolChunk分配内存。分配完成并且初始化后,将PoolChunk加入qInit链表中,并移动到符合使用率的链表中。

分配成功后,会在PoolArena中将对应计数器自增。

申请huge级别内存

由于huge级别的内存过大,不适合池化管理,所以申请的是unpooled的内存。申请完huge内存后,依然进行初始化和计数。

private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
PoolChunk<T> chunk = newUnpooledChunk(reqCapacity);
activeBytesHuge.add(chunk.chunkSize());
buf.initUnpooled(chunk, reqCapacity);
allocationsHuge.increment();
}

10.PoolArena的更多相关文章

  1. Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级

    Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part3:db安装和升级 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 5.安装Database软件 5. ...

  2. Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作

    Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 1.实施前准备工作 1.1 服务器安装操 ...

  3. ArcGIS 10.0紧凑型切片读写方法

    首先介绍一下ArcGIS10.0的缓存机制: 切片方案 切片方案包括缓存的比例级别.切片尺寸和切片原点.这些属性定义缓存边界的存在位置,在某些客户端中叠加缓存时匹配这些属性十分重要.图像格式和抗锯齿等 ...

  4. CI Weekly #10 | 2017 DevOps 趋势预测

    2016 年的最后几个工作日,我们对 flow.ci Android & iOS 项目做了一些优化与修复: iOS 镜像 cocoapods 版本更新: fir iOS上传插件时间问题修复: ...

  5. 10个最好用的HTML/CSS 工具、插件和资料库

    大家在使用HTML/CSS开发项目的过程中,有使用过哪些工具,插件和库?下面介绍的10种HTML/CSS工具,插件和资料库,是国外程序员经常用到的. Firebug Lite FirebugLite ...

  6. 【Win 10 应用开发】启动远程设备上的应用

    这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...

  7. 在Ubuntu 16.10安装mysql workbench报未安装软件包 libpng12-0错误

    1.安装mysql workbench,提示未安装软件包 libpng12-0 下载了MySQL Workbench 6.3.8   在安装的时候报错: -1ubu1604-amd64.deb 提示: ...

  8. Ubuntu 16.10 安装byzanz截取动态效果图工具

    1.了解byzanz截取动态效果图工具 byzanz能制作文件小,清晰的GIF动态效果图,不足就是,目前只能通过输入命令方式来录制. byzanz主要的参数选项有: -d, --duration=SE ...

  9. Ubuntu 16.10 安装KolourPaint 4画图工具

    KolourPaint 4画图工具简单实用,可以绘画.视频处理和图标编辑: • 绘画:绘制图表和“手绘” • 视频处理:编辑截图和照片;应用特效 • 图标编辑:绘画剪贴和标识透明化 1.在Ubuntu ...

随机推荐

  1. windows driver 获取文件属性

    OBJECT_ATTRIBUTES oa; FILE_NETWORK_OPEN_INFORMATION fnoi; UNICODE_STRING strPath = RTL_CONSTANT_STRI ...

  2. 面试官,不要再问我“Java 垃圾收集器”了(转载)

    如果Java虚拟机中标记清除算法.标记整理算法.复制算法.分代算法这些属于GC收集算法中的方法论,那么"GC收集器"则是这些方法论的具体实现. 在 面试过程中这个深度的问题涉及的比 ...

  3. JPA 开发中遇到的错误

    JPA 开发中遇到的错误 (2011-07-13 16:56:12) 转载▼ 标签: 杂谈 分类: Java/J2EE 常见异常1.异常信息:org.hibernate.hql.ast.QuerySy ...

  4. Codeforces Round #604 (Div. 2) 部分题解

    链接:http://codeforces.com/contest/1265 A. Beautiful String A string is called beautiful if no two con ...

  5. English Words and Expressions

    Words apprentice 学徒 corny 老套的 obnoxious 作呕的 intermezzo 间奏曲 predate 日期上早于 parenthesis 括号 delimiter 分隔 ...

  6. [极客大挑战 2019]HardSQL

    0x00 知识点 报错注入 链接: https://www.cnblogs.com/richardlee97/p/10617115.html 报错原因: 其原因主要是因为虚拟表的主键重复.按照MySQ ...

  7. 自己编写DLL并导出函数

    sub.c #include<windows.h> #include"sub.h" int WINAPI DllMain(_In_ HANDLE _HDllHandle ...

  8. 吴裕雄--天生自然 JAVASCRIPT开发学习:函数调用

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. cmd定时自动弹窗命令

    at 17:00 /e:m,t,w,th,f,s,su msg * 弹窗文字

  10. JavaWeb之Servlet入门(二)

    1. 准备 在JavaWeb之Servlet入门(一)中,我们完成了第一个Servlet程序,完成了从URL到后台控制器的中转过程,接下来我们延续JavaWeb之Servlet入门(一)学习下如何传参 ...