Netty源码分析(七) PoolChunk

在分析源码之前,我们先来了解一下Netty的内存管理机制。我们知道,jvm是自动管理内存的,这带来了一些好处,在分配内存的时候可以方便管理,也带来了一些问题。jvm每次分配内存的时候,都是先要去堆上申请内存空间进行分配,这就带来了很大的性能上的开销。当然,也可以使用堆外内存,Netty就用了堆外内存,但是内存的申请和释放,依然需要性能的开销。所以Netty实现了内存池来管理内存的申请和使用,提高了内存使用的效率。
PoolChunk就是Netty的内存管理的一种实现。Netty一次向系统申请16M的连续内存空间,这块内存通过PoolChunk对象包装,为了更细粒度的管理它,进一步的把这16M内存分成了2048个页(pageSize=8k)。页作为Netty内存管理的最基本的单位 ,所有的内存分配首先必须申请一块空闲页。Ps: 这里可能有一个疑问,如果申请1Byte的空间就分配一个页是不是太浪费空间,在Netty中Page还会被细化用于专门处理小于4096Byte的空间申请 那么这些Page需要通过某种数据结构跟算法管理起来。
先来看看PoolChunk有哪些属性

/**
 * 所属 Arena 对象
 */
final PoolArena<T> arena;
/**
 * 内存空间。
 *
 * @see PooledByteBuf#memory
 */
final T memory;
/**
 * 是否非池化
 *
 * @see #PoolChunk(PoolArena, Object, int, int) 非池化。当申请的内存大小为 Huge 类型时,创建一整块 Chunk ,并且不拆分成若干 Page
 * @see #PoolChunk(PoolArena, Object, int, int, int, int, int) 池化
 */
final boolean unpooled;

final int offset;

/**
 * 分配信息满二叉树
 *
 * index 为节点编号
 */
private final byte[] memoryMap;
/**
 * 高度信息满二叉树
 *
 * index 为节点编号
 */
private final byte[] depthMap;
/**
 * PoolSubpage 数组
 */
private final PoolSubpage<T>[] subpages;
/**
 * 判断分配请求内存是否为 Tiny/Small ,即分配 Subpage 内存块。
 *
 * Used to determine if the requested capacity is equal to or greater than pageSize.
 */
private final int subpageOverflowMask;
/**
 * Page 大小,默认 8KB = 8192B
 */
private final int pageSize;
/**
 * 从 1 开始左移到 {@link #pageSize} 的位数。默认 13 ,1 << 13 = 8192 。
 *
 * 具体用途,见 {@link #allocateRun(int)} 方法,计算指定容量所在满二叉树的层级。
 */
private final int pageShifts;
/**
 * 满二叉树的高度。默认为 11 。
 */
private final int maxOrder;
/**
 * Chunk 内存块占用大小。默认为 16M = 16 * 1024  。
 */
private final int chunkSize;
/**
 * log2 {@link #chunkSize} 的结果。默认为 log2( 16M ) = 24 。
 */
private final int log2ChunkSize;
/**
 * 可分配 {@link #subpages} 的数量,即数组大小。默认为 1 << maxOrder = 1 << 11 = 2048 。
 */
private final int maxSubpageAllocs;

Netty采用完全二叉树进行管理,树中每个叶子节点表示一个Page,即树高为12,中间节点表示页节点的持有者。有了上面的数据结构,那么页的申请跟释放就非常简单了,只需要从根节点一路遍历找到可用的节点即可。主要来看看PoolChunk是怎么分配内存的。

long allocate(int normCapacity) {
    // 大于等于 Page 大小,分配 Page 内存块
    if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
        return allocateRun(normCapacity);
    // 小于 Page 大小,分配 Subpage 内存块
    } else {
        return allocateSubpage(normCapacity);
    }
}

 private long allocateRun(int normCapacity) {
    // 获得层级
    int d = maxOrder - (log2(normCapacity) - pageShifts);
    // 获得节点
    int id = allocateNode(d);
    // 未获得到节点,直接返回
    if (id < 0) {
        return id;
    }
    // 减少剩余可用字节数
    freeBytes -= runLength(id);
    return id;
}

private long allocateSubpage(int normCapacity) {
    // 获得对应内存规格的 Subpage 双向链表的 head 节点
    // Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
    // This is need as we may add it back and so alter the linked-list structure.
    PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);
    // 加锁,分配过程会修改双向链表的结构,会存在多线程的情况。
    synchronized (head) {
        // 获得最底层的一个节点。Subpage 只能使用二叉树的最底层的节点。
        int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
        int id = allocateNode(d);
        // 获取失败,直接返回
        if (id < 0) {
            return id;
        }

        final PoolSubpage<T>[] subpages = this.subpages;
        final int pageSize = this.pageSize;

        // 减少剩余可用字节数
        freeBytes -= pageSize;

        // 获得节点对应的 subpages 数组的编号
        int subpageIdx = subpageIdx(id);
        // 获得节点对应的 subpages 数组的 PoolSubpage 对象
        PoolSubpage<T> subpage = subpages[subpageIdx];
        // 初始化 PoolSubpage 对象
        if (subpage == null) { // 不存在,则进行创建 PoolSubpage 对象
            subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
            subpages[subpageIdx] = subpage;
        } else { // 存在,则重新初始化 PoolSubpage 对象
            subpage.init(head, normCapacity);
        }
        // 分配 PoolSubpage 内存块
        return subpage.allocate();
    }
}

Netty的内存按大小分为tiny,small,normal,而类型上可以分为PoolChunk,PoolSubpage,小于4096大小的内存就被分成PoolSubpage。Netty就是这样实现了对内存的管理。
PoolChunk就分析到这里了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 但是内存拷贝对性能有可能影响比较大,所以Java中可以绕开堆内存直接操作堆外内存,问题是创建堆外内存的速度比堆内存...
    zxRay阅读 7,375评论 2 32
  • 真实内存指的是ByteBuf底层的array和DirectByteBuffer,真实内存池化的好处如下:1.1 降...
    YDDMAX_Y阅读 3,069评论 2 7
  • ByteBuf基础 Java Nio 的Buffer 在进行数据传输的过程中,我们经常会用到缓冲区。在Java N...
    达微阅读 1,268评论 0 2
  • Netty之ByteBuf深入分析 [TOC] 分析思路 内存与内存管理器的抽象 ByteBuf 结构以及重要的A...
    石家志远阅读 2,313评论 0 1
  • 自从在简书领了日更的任务,每次写作都会拖到晚上10点之后,昨天晚上写到12点才算完任务,代价就是失眠了。 看看今天...
    龙航007阅读 277评论 0 0