Netty之Jemalloc(二)PoolSubpage

上次讲Jemalloc 算法将每个 Chunk 切分成多个小块 Page,但是Page还是较大的内存块,所以直接使用仍旧浪费。因此,Jemalloc 算法将每个 Page 更进一步的切分为多个 Subpage 内存块。Page 切分成多个 Subpage 内存块,直接基于数组,通过数组来标记每个 Subpage 内存块是否已经分配,这里用PoolSubpage解决这个问题。
一个page切分成多个大小均等的Subpage内存块。每个 Page 拆分的 Subpage 内存块可以不同,以 Page 第一次拆分为 Subpage 内存块时请求分配的内存大小为准。(比如说,申请一个 16B 的内存块,那么 Page0 被拆成成 ( 8KB / 16B )512个 Subpage 块,使用第 0 块。然后,申请一个 32B 的内存块,那么 Page1 被拆分成 ( 8KB / 32B )256个 Subpage 块,使用第 0 块。最后,申请一个 16B 的内存块,那么重用 Page0 ,使用第 1 块。看不明白的话去上一篇看图就明白了嗷)
总结一下就是说Subpage内存块被申请时,先去找大小匹配且可分配的page,如果有就使用其中一块Subpage,如果没有则选择下一个新的page进行拆分,使用新Page的第0块Subpage。
PoolSubpage ,实现 PoolSubpageMetric 接口,Netty 对 Jemalloc Subpage 的实现类。
构造方法:

/**
 * 所属 PoolChunk 对象
 */
final PoolChunk<T> chunk;
/**
 * 在 {@link PoolChunk#memoryMap} 的节点编号
 */
private final int memoryMapIdx;
/**
 * 在 Chunk 中,偏移字节量
 *
 * @see PoolChunk#runOffset(int) 
 */
private final int runOffset;
/**
 * Page 大小 {@link PoolChunk#pageSize}
 */
private final int pageSize;

/**
 * Subpage 分配信息数组
 *
 * 每个 long 的 bits 位代表一个 Subpage 是否分配。
 * 因为 PoolSubpage 可能会超过 64 个( long 的 bits 位数 ),所以使用数组。
 *   例如:Page 默认大小为 8KB ,Subpage 默认最小为 16 B ,所以一个 Page 最多可包含 8 * 1024 / 16 = 512 个 Subpage 。
 *        因此,bitmap 数组大小为 512 / 64 = 8 。
 * 另外,bitmap 的数组大小,使用 {@link #bitmapLength} 来标记。或者说,bitmap 数组,默认按照 Subpage 的大小为 16B 来初始化。
 *    为什么是这样的设定呢?因为 PoolSubpage 可重用,通过 {@link #init(PoolSubpage, int)} 进行重新初始化。
 */
private final long[] bitmap;

/**
 * 双向链表,前一个 PoolSubpage 对象
 */
PoolSubpage<T> prev;
/**
 * 双向链表,后一个 PoolSubpage 对象
 */
PoolSubpage<T> next;

/**
 * 是否未销毁
 */
boolean doNotDestroy;
/**
 * 每个 Subpage 的占用内存大小,例如 16B、32B 等等。
 */
int elemSize;
/**
 * 总共 Subpage 的数量
 */
private int maxNumElems;
/**
 * {@link #bitmap} 长度,bitmap 数组的真正使用的数组大小。
 */
private int bitmapLength;
/**
 * 下一个可分配 Subpage 的数组位置
 */
private int nextAvail;
/**
 * 剩余可用 Subpage 的数量
 */
private int numAvail;

  // 构造方法 1: 双向链表,头节点
  /** Special constructor that creates a linked list head */
  PoolSubpage(int pageSize) {
      chunk = null;
      //memoryMapIdx 属性,在 PoolChunk.memoryMap 的节点编号,例如节点编号 2048 。
      memoryMapIdx = -1;
      //runOffset 属性,在 Chunk 中,偏移字节量,通过 PoolChunk的runOffset(id) 方法计算。在 PoolSubpage 中,无相关的逻辑,仅用于 toString() 方法,打印信息。
      runOffset = -1;
      elemSize = -1;
      this.pageSize = pageSize;
      bitmap = null;
  }
  
  // 构造方法 2: 双向链表,Page 节点
  PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
      this.chunk = chunk;
      this.memoryMapIdx = memoryMapIdx;
      this.runOffset = runOffset;
      this.pageSize = pageSize;
      // 创建 bitmap 数组
      bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
      // 初始化
      init(head, elemSize);
  }

init(PoolSubpage head, int elemSize) 方法,初始化:

 void init(PoolSubpage<T> head, int elemSize) {
     // 未销毁
     doNotDestroy = true;
     // 初始化 elemSize
     this.elemSize = elemSize;
     if (elemSize != 0) {
         // 初始化 maxNumElems
         maxNumElems = numAvail = pageSize / elemSize;
         // 初始化 nextAvail
         nextAvail = 0;
         // 计算 bitmapLength 的大小
         bitmapLength = maxNumElems >>> 6;
         if ((maxNumElems & 63) != 0) { // 未整除,补 1.
             bitmapLength ++;
         }
 
         // 初始化 bitmap
         for (int i = 0; i < bitmapLength; i ++) {
             bitmap[i] = 0;
         }
     }
     // 添加到 Arena 的双向链表中。
     addToPool(head);
 }

在每个 Arena 中,有 tinySubpagePools 和 smallSubpagePools 属性,分别表示 tiny 和 small 类型的 PoolSubpage 数组(Subpage的内存规格分为Tiny 和 Small 两类,并且每类有多种大小,16B - 496B算Tiny,512B - 4KB算Small):

// PoolArena.java

/**
 * tiny 类型的 PoolSubpage 数组
 *
 * 数组的每个元素,都是双向链表
 */
private final PoolSubpage<T>[] tinySubpagePools;
/**
 * small 类型的 SubpagePools 数组
 *
 * 数组的每个元素,都是双向链表
 */
private final PoolSubpage<T>[] smallSubpagePools;

数组的每个元素,通过 prev 和 next 属性,形成双向链表。
每个元素,表示对应的 Subpage 内存规格的双向链表。
例如:tinySubpagePools[0] 表示 16B ,tinySubpagePools[1] 表示 32B 。
根据tinySubpagePools 和 smallSubpagePools 属性,可以从中查找,是否已经有符合分配内存规格的 Subpage 节点可分配。

初始时,每个双向链表,会创建对应的 head 节点:

// PoolArena.java

private PoolSubpage<T> newSubpagePoolHead(int pageSize) {
    PoolSubpage<T> head = new PoolSubpage<T>(pageSize);
    head.prev = head;
    head.next = head;
    return head;
}

head 的上下节点都是自己。(这是个循环链表。)

addToPool(PoolSubpage head) 方法中,添加到 Arena 的双向链表中:

private void addToPool(PoolSubpage<T> head) {
    assert prev == null && next == null;
    // 将当前节点,插入到 head 和 head.next 中间
    prev = head;
    next = head.next;
    next.prev = this;
    head.next = this;
}

removeFromPool() 方法中,从双向链表中移除:

private void removeFromPool() {
    assert prev != null && next != null;
    // 前后节点,互相指向
    prev.next = next;
    next.prev = prev;
    // 当前节点,置空
    next = null;
    prev = null;
}

allocate() 方法,分配一个 Subpage 内存块,并返回该内存块的位置 handle :

 long allocate() {
     // 防御性编程,不存在这种情况。
     if (elemSize == 0) {
         return toHandle(0);
     }
 
     // 可用数量为 0 ,或者已销毁,返回 -1 ,即不可分配。
     if (numAvail == 0 || !doNotDestroy) {
         return -1;
     }
 
     // 获得下一个可用的 Subpage 在 bitmap 中的总体位置
     final int bitmapIdx = getNextAvail();
     // 获得下一个可用的 Subpage 在 bitmap 中数组的位置(bitmapIdx / 64)
     int q = bitmapIdx >>> 6;
     // 获得下一个可用的 Subpage 在 bitmap 中数组的位置的第几 bits(bitmapIdx % 64 )
     int r = bitmapIdx & 63;
     assert (bitmap[q] >>> r & 1) == 0;
     // 修改 Subpage 在 bitmap 中不可分配。
     bitmap[q] |= 1L << r;
 
     // 可用 Subpage 内存块的计数减一
     if (-- numAvail == 0) { // 无可用 Subpage 内存块
         // 从双向链表中移除
         removeFromPool();
     }
 
     // 计算 handle
     return toHandle(bitmapIdx);
 }

调用 toHandle(bitmapIdx) 方法,计算 handle 值。

private long toHandle(int bitmapIdx) {
    return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
}

free(PoolSubpage head, int bitmapIdx) 方法,释放指定位置的 Subpage 内存块,并返回当前 Page 是否正在使用中( true ):
如果不再使用,可以将该节点( Page )从 Chunk 中释放,标记为可用

 boolean free(PoolSubpage<T> head, int bitmapIdx) {
          if (elemSize == 0) {
         return true;
     }
     // 获得 Subpage 在 bitmap 中数组的位置
     int q = bitmapIdx >>> 6;
     // 获得 Subpage 在 bitmap 中数组的位置的第几 bits
     int r = bitmapIdx & 63;
     assert (bitmap[q] >>> r & 1) != 0;
     // 修改 Subpage 在 bitmap 中可分配。
     bitmap[q] ^= 1L << r;
 
     // 设置下一个可用为当前 Subpage
     setNextAvail(bitmapIdx);
 
     // 可用 Subpage 内存块的计数加一
     if (numAvail ++ == 0) {
         // 添加到 Arena 的双向链表中。
         addToPool(head);
         return true;
     }
 
     // 还有 Subpage 在使用
     if (numAvail != maxNumElems) {
         return true;
     // 没有 Subpage 在使用
     } else {
         // 双向链表中,只有该节点,不进行移除
         // 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;
     }
 }

调用 setNextAvail(int bitmapIdx) 方法,设置下一个可用为当前 Subpage 的位置:

private void setNextAvail(int bitmapIdx) {
    nextAvail = bitmapIdx;
}

getNextAvail() 方法,获得下一个可用的 Subpage 在 bitmap 中的总体位置:

private int getNextAvail() {
    int nextAvail = this.nextAvail;
    //  nextAvail 大于 0 ,意味着已经“缓存”好下一个可用的位置,直接返回即可。
    if (nextAvail >= 0) {
        this.nextAvail = -1;
        return nextAvail;
    }
    //  寻找下一个 nextAvail
    return findNextAvail();
}

findNextAvail() 方法,寻找下一个 nextAvail:

private int findNextAvail() {
    final long[] bitmap = this.bitmap;
    final int bitmapLength = this.bitmapLength;
    // 循环 bitmap
    for (int i = 0; i < bitmapLength; i ++) {
        long bits = bitmap[i];
        // ~ 操作,如果不等于 0 ,说明有可用的 Subpage
        if (~bits != 0) {
            // 在这 bits 寻找可用 nextAvail
            return findNextAvail0(i, bits);
        }
    }
    // 未找到
    return -1;
}

findNextAvail0(int i, long bits) 方法,在这 bits 寻找可用 nextAvail :

 private int findNextAvail0(int i, long bits) {
     final int maxNumElems = this.maxNumElems;
     // 计算基础值,表示在 bitmap 的数组下标
     final int baseVal = i << 6; // 相当于 * 64
 
     // 遍历 64 bits
     for (int j = 0; j < 64; j ++) {
         // 计算当前 bit 是否未分配
         if ((bits & 1) == 0) {
             // 可能 bitmap 最后一个元素,并没有 64 位,通过 baseVal | j < maxNumElems 来保证不超过上限。
             int val = baseVal | j;
             if (val < maxNumElems) {
                 return val;
             } else {
                 break;
             }
         }
         // 去掉当前 bit
         bits >>>= 1;
     }
 
     // 未找到
     return -1;
 }

destroy() 方法,销毁:

void destroy() {
    if (chunk != null) {
        chunk.destroy();
    }
}

PoolSubpageMetric ,PoolSubpage Metric 接口:

public interface PoolSubpageMetric {

    /**
     * Return the number of maximal elements that can be allocated out of the sub-page.
     */
    int maxNumElements();

    /**
     * Return the number of available elements to be allocated.
     */
    int numAvailable();

    /**
     * Return the size (in bytes) of the elements that will be allocated.
     */
    int elementSize();

    /**
     * Return the size (in bytes) of this page.
     */
    int pageSize();
}

PoolChunk 对 PoolChunkMetric 接口的实现:

@Override
public int maxNumElements() {
    synchronized (chunk.arena) {
        return maxNumElems;
    }
}

@Override
public int numAvailable() {
    synchronized (chunk.arena) {
        return numAvail;
    }
}

@Override
public int elementSize() {
    synchronized (chunk.arena) {
        return elemSize;
    }
}

@Override
public int pageSize() {
    return pageSize;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值