PoolArena

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

PoolArena成员介绍

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

  1. protected PoolArena(PooledByteBufAllocator parent, int pageSize,
  2. int maxOrder, int pageShifts, int chunkSize, int cacheAlignment) {
  3. this.parent = parent;
  4. this.pageSize = pageSize;
  5. this.maxOrder = maxOrder;
  6. this.pageShifts = pageShifts;
  7. this.chunkSize = chunkSize;
  8. directMemoryCacheAlignment = cacheAlignment;
  9. directMemoryCacheAlignmentMask = cacheAlignment - 1;
  10. subpageOverflowMask = ~(pageSize - 1);
  11. tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);
  12. for (int i = 0; i < tinySubpagePools.length; i ++) {
  13. tinySubpagePools[i] = newSubpagePoolHead(pageSize);
  14. }
  15. numSmallSubpagePools = pageShifts - 9;
  16. smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
  17. for (int i = 0; i < smallSubpagePools.length; i ++) {
  18. smallSubpagePools[i] = newSubpagePoolHead(pageSize);
  19. }
  20. q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, chunkSize);
  21. q075 = new PoolChunkList<T>(this, q100, 75, 100, chunkSize);
  22. q050 = new PoolChunkList<T>(this, q075, 50, 100, chunkSize);
  23. q025 = new PoolChunkList<T>(this, q050, 25, 75, chunkSize);
  24. q000 = new PoolChunkList<T>(this, q025, 1, 50, chunkSize);
  25. qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, chunkSize);
  26. q100.prevList(q075);
  27. q075.prevList(q050);
  28. q050.prevList(q025);
  29. q025.prevList(q000);
  30. q000.prevList(null);
  31. qInit.prevList(qInit);

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

PoolChunkList

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

PoolChunkList实例化

  1. PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage, int chunkSize) {
  2. this.arena = arena;
  3. this.nextList = nextList;
  4. this.minUsage = minUsage;
  5. this.maxUsage = maxUsage;
  6. maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
  7. }
  8. private static int calculateMaxCapacity(int minUsage, int chunkSize) {
  9. minUsage = Math.max(1, minUsage);
  10. if (minUsage == 100) {
  11. return 0;
  12. }
  13. return (int) (chunkSize * (100L - minUsage) / 100L);
  14. }

在构造函数中定义了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不满足内存使用率,递归调用。
  1. void add(PoolChunk<T> chunk) {
  2. if (chunk.usage() >= maxUsage) {
  3. nextList.add(chunk);
  4. return;
  5. }
  6. add0(chunk);
  7. }
  8. void add0(PoolChunk<T> chunk) {
  9. chunk.parent = this;
  10. if (head == null) {
  11. head = chunk;
  12. chunk.prev = null;
  13. chunk.next = null;
  14. } else {
  15. chunk.prev = null;
  16. chunk.next = head;
  17. head.prev = chunk;
  18. head = chunk;
  19. }
  20. }

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

PoolChunkList移动PoolChunk

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

  1. private boolean move0(PoolChunk<T> chunk) {
  2. if (prevList == null) {
  3. assert chunk.usage() == 0;
  4. return false;
  5. }
  6. return prevList.move(chunk);
  7. }
  8. private boolean move(PoolChunk<T> chunk) {
  9. if (chunk.usage() < minUsage) {
  10. return move0(chunk);
  11. }
  12. add0(chunk);
  13. return true;
  14. }

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

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

PoolChunkList申请内存

  1. boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
  2. if (normCapacity > maxCapacity) {
  3. return false;
  4. }
  5. for (PoolChunk<T> cur = head; cur != null; cur = cur.next) {
  6. if (cur.allocate(buf, reqCapacity, normCapacity)) {
  7. if (cur.usage() >= maxUsage) {
  8. remove(cur);
  9. nextList.add(cur);
  10. }
  11. return true;
  12. }
  13. }
  14. return false;
  15. }

首先注意到有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)方法来申请内存。

  1. PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
  2. PooledByteBuf<T> buf = newByteBuf(maxCapacity);
  3. allocate(cache, buf, reqCapacity);
  4. return buf;
  5. }

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

  1. private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
  2. final int normCapacity = normalizeCapacity(reqCapacity);
  3. if (isTinyOrSmall(normCapacity)) {
  4. int tableIdx;
  5. PoolSubpage<T>[] table;
  6. boolean tiny = isTiny(normCapacity);
  7. if (tiny) {
  8. if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
  9. return;
  10. }
  11. tableIdx = tinyIdx(normCapacity);
  12. table = tinySubpagePools;
  13. } else {
  14. if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
  15. return;
  16. }
  17. tableIdx = smallIdx(normCapacity);
  18. table = smallSubpagePools;
  19. }
  20. final PoolSubpage<T> head = table[tableIdx];
  21. synchronized (head) {
  22. final PoolSubpage<T> s = head.next;
  23. if (s != head) {
  24. assert s.doNotDestroy && s.elemSize == normCapacity;
  25. long handle = s.allocate();
  26. assert handle >= 0;
  27. s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity);
  28. incTinySmallAllocation(tiny);
  29. return;
  30. }
  31. }
  32. synchronized (this) {
  33. allocateNormal(buf, reqCapacity, normCapacity);
  34. }
  35. incTinySmallAllocation(tiny);
  36. return;
  37. }
  38. if (normCapacity <= chunkSize) {
  39. if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
  40. return;
  41. }
  42. synchronized (this) {
  43. allocateNormal(buf, reqCapacity, normCapacity);
  44. ++allocationsNormal;
  45. }
  46. } else {
  47. allocateHuge(buf, reqCapacity);
  48. }
  49. }

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

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

将申请内存规整化。

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

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

  1. int normalizeCapacity(int reqCapacity) {
  2. checkPositiveOrZero(reqCapacity, "reqCapacity");
  3. if (reqCapacity >= chunkSize) {
  4. return reqCapacity;
  5. }
  6. if (!isTiny(reqCapacity)) {
  7. int normalizedCapacity = reqCapacity;
  8. normalizedCapacity --;
  9. normalizedCapacity |= normalizedCapacity >>> 1;
  10. normalizedCapacity |= normalizedCapacity >>> 2;
  11. normalizedCapacity |= normalizedCapacity >>> 4;
  12. normalizedCapacity |= normalizedCapacity >>> 8;
  13. normalizedCapacity |= normalizedCapacity >>> 16;
  14. normalizedCapacity ++;
  15. if (normalizedCapacity < 0) {
  16. normalizedCapacity >>>= 1;
  17. }
  18. return normalizedCapacity;
  19. }
  20. if ((reqCapacity & 15) == 0) {
  21. return reqCapacity;
  22. }
  23. return (reqCapacity & ~15) + 16;
  24. }

首先检验了内存是否不是负数,否则直接抛出异常。之后判断申请内存是否超出了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对象加锁,防止其他线程同时分配内存。具体分配的代码如下

  1. private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
  2. if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
  3. q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
  4. q075.allocate(buf, reqCapacity, normCapacity)) {
  5. return;
  6. }
  7. // Add a new chunk.
  8. PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
  9. boolean success = c.allocate(buf, reqCapacity, normCapacity);
  10. assert success;
  11. qInit.add(c);
  12. }

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

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

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

申请huge级别内存

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

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

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. 关于SI4432的问题简单讲解

    对于SX1278 和SI4432的对比性,下面为大家展示对比参数: 由此可以看出的SI4432虽然跟SX1278有部分地方不同,但是整体来说还是差别不大,各有各的长处和短处,性价比上个人还是觉得SI4 ...

  2. zabbix 日志

    /var/log/zabbix/ tail -f /var/log/zabbix/zabbix_server.log tail -f /var/log/zabbix/zabbix_agentd.log

  3. 屏幕切换 onStart() onStop() onRestart() onDestroy()

    android:configChanges="orientation|keyboardHidden|screenSize"          //xml文件<activity ...

  4. maven学习(三)-使用maven来创建项目

    转自https://www.cnblogs.com/xdp-gacl/p/4240930.html maven作为一个高度自动化构建工具,本身提供了构建项目的功能,下面就来体验一下使用maven构建项 ...

  5. cf1200 D White Lines(二维差分)

    题目大意 有一个大小为n的矩阵,每个1*1的单位为黑或白,我们可以用一个(只有一个)大小为k*k的白色矩阵覆盖,问:最多的时候有几条白线(横的全为白 或竖的全为白 即为白线). 思路 要想把一条线(以 ...

  6. python数据拼接: pd.concat

    1.concat concat函数是在pandas底下的方法,可以将数据根据不同的轴作简单的融合 pd.concat(objs, axis=0, join='outer', join_axes=Non ...

  7. Java Web应用的加载过程

    在介绍Spring IoC和MVC的加载前,用这篇小文章简单地记录下,最简单的web应用的加载过程. 一.从最简单的web应用出发 使用Eclipse直接创建一个Dynamic Web Project ...

  8. Java web实现综合查询+SQL语句拼接

    首先展示图形界: 界面比较简单,但麻雀虽小五脏俱全.因为数据库只有六种数据类型,所以最多添加六个查询框. 测试以下问题: 删除方式是从上往下开始的,如果删除之后会有问题.(后续改进ing) 若干个并且 ...

  9. Servlet过滤器基础及使用场景

    Servlet过滤器详解 一.过滤器基础 1.Servlet过滤器是Servlet的一种特殊用法,主要用来完成一些通用的操作.比如编码的过滤,判断用户的登陆状态等等.Servlet过滤器的适用场合: ...

  10. HDU 1588 矩阵快速幂 嵌套矩阵

    这个题目搞了我差不多一个下午,之前自己推出一个公式,即 f[n+k]=k*f[n]+f[n-1]结果发现根本不能用,无法降低复杂度. 后来又个博客的做法相当叼,就按他的做法来了 即 最终求得是 S(n ...