10.PoolArena
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种情况被调用:
- 新创建的Chunk,由一个特殊的PoolChunkList——qInit调用,加入链表中。
- 在chunk内存使用率变化时,需要调整其所在链表时调用
- 调整链表时,发现当前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);
}
}
可以看出这个方法是相当的长,但总体逻辑还算清晰,惯例先分步骤:
- 将申请内存规整化。
- 申请tiny、small级别的内存。
- 申请normal级别的内存。
- 申请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的更多相关文章
- 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. ...
- 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 服务器安装操 ...
- ArcGIS 10.0紧凑型切片读写方法
首先介绍一下ArcGIS10.0的缓存机制: 切片方案 切片方案包括缓存的比例级别.切片尺寸和切片原点.这些属性定义缓存边界的存在位置,在某些客户端中叠加缓存时匹配这些属性十分重要.图像格式和抗锯齿等 ...
- CI Weekly #10 | 2017 DevOps 趋势预测
2016 年的最后几个工作日,我们对 flow.ci Android & iOS 项目做了一些优化与修复: iOS 镜像 cocoapods 版本更新: fir iOS上传插件时间问题修复: ...
- 10个最好用的HTML/CSS 工具、插件和资料库
大家在使用HTML/CSS开发项目的过程中,有使用过哪些工具,插件和库?下面介绍的10种HTML/CSS工具,插件和资料库,是国外程序员经常用到的. Firebug Lite FirebugLite ...
- 【Win 10 应用开发】启动远程设备上的应用
这个功能必须在“红石-1”(build 14393)以上的系统版中才能使用,运行在一台设备上的应用,可以通过URI来启动另一台设备上的应用.激活远程应用需要以下前提: 系统必须是build 14393 ...
- 在Ubuntu 16.10安装mysql workbench报未安装软件包 libpng12-0错误
1.安装mysql workbench,提示未安装软件包 libpng12-0 下载了MySQL Workbench 6.3.8 在安装的时候报错: -1ubu1604-amd64.deb 提示: ...
- Ubuntu 16.10 安装byzanz截取动态效果图工具
1.了解byzanz截取动态效果图工具 byzanz能制作文件小,清晰的GIF动态效果图,不足就是,目前只能通过输入命令方式来录制. byzanz主要的参数选项有: -d, --duration=SE ...
- Ubuntu 16.10 安装KolourPaint 4画图工具
KolourPaint 4画图工具简单实用,可以绘画.视频处理和图标编辑: • 绘画:绘制图表和“手绘” • 视频处理:编辑截图和照片;应用特效 • 图标编辑:绘画剪贴和标识透明化 1.在Ubuntu ...
随机推荐
- P 1033 旧键盘打字
转跳点:
- gcc/g++以c++11的方式编译
方法一: 在程序头加上预定义编译器命令 #pragma GCC diagnostic error "-std=c++11" 通过#pragma 指示 GCC编译器处理错误的方式以c ...
- 下页小希学MVC5+EF6.2 学习记录二
目的:1 学习mvc+ef 2 写下日记也是对自己的督促 从前端的UI开始 MVC分离的比较好,开发顺序没有特别要求,先开发哪一部分都可以,这次我们主要讲解前端UI的部分. ASP.NET MVC抛 ...
- 苹果maccms网站漏洞进行修复解决方法教程
上期发现视频名称中包含木马文件 然后教大家使用标签进行过滤 {$vo.vod_name|mac_filter_html} 可是最近发现现在黑客行为是全字段挂马,修改数据字段格式挂马,多重嵌套挂马. 真 ...
- [题解] Luogu P5641 【CSGRound2】开拓者的卓识
这个柿子挺别致的......还有信仰膜数998244353 直接讲正解吧...... 首先发现这个柿子从上往下算好像不怎么行,我们从下往上看,(下面令\(Ans_r = sum_{k,1,r}\)). ...
- centos 制作指定需求命令的YUM源
场景: 没有YUM源,但是需要安装一些用到的命令,如vim,telnet等少量命令,不想YUM源太大,满足需求即可.于是制作一个仅需要满足要求的yum源 步骤一: 联网环境下安装createreo命令 ...
- id就是方法名,如何调用;批量input怎么获取他们的key值作为参数
1.很多Dom的时候,一个个写会比较麻烦,我用ID记载他的方法名: 2.很多input,在数据交互的时候一个个获取会比较繁琐,给一个方法,批量获取. <div id="searchSt ...
- jsch通过SSH2执行linux命令
public class SSHUtils { private Channel channel; private Session session = null; private int timeout ...
- python里的property修饰器
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改: s = Student() s.score = 9999 这显然不合逻辑.为了限制score的 ...
- UVALive 6491 You win! 状态DP
这个题目上周的对抗赛的,美国2013区域赛的题目,上次比赛真惨,就做出一道题,最多的也只做出两道,当时想把这题做出来,一直TLE. 这个题目用挂在Hunnu OJ的数据可以过,但UVALive上死活过 ...