Netty源码解析 -- PoolSubpage实现原理
前面文章说了PoolChunk如何管理Normal内存块,本文分享PoolSubpage如何管理Small内存块。
源码分析基于Netty 4.1.52
内存管理算法
PoolSubpage负责管理Small内存块。一个PoolSubpage中的内存块size都相同,该size对应SizeClasses#sizeClasses表格的一个索引index。
新创建的PoolSubpage都必须加入到PoolArena#smallSubpagePools[index]链表中。
PoolArena#smallSubpagePools是一个PoolSubpage数组,数组中每个元素都是一个PoolSubpage链表,PoolSubpage之间可以通过next,prev组成链表。
感兴趣的同学可以参考《内存对齐类SizeClasses》。
注意,Small内存size并不一定小于pageSize(默认为8K)
默认Small内存size <= 28672(28KB)
关于Normal内存块,Small内存块,pageSize,可参考《PoolChunk实现原理》。
PoolSubpage实际上就是PoolChunk中的一个Normal内存块,大小为其管理的内存块size与pageSize最小公倍数。
PoolSubpage使用位图的方式管理内存块。
PoolSubpage#bitmap是一个long数组,其中每个long元素上每个bit位都可以代表一个内存块是否使用。
内存分配
分配Small内存块有两个步骤
- PoolChunk中分配PoolSubpage。
 如果PoolArena#smallSubpagePools中已经有对应的PoolSubpage缓冲,则不需要该步骤。
- PoolSubpage上分配内存块
PoolChunk#allocateSubpage
private long allocateSubpage(int sizeIdx) {
    // #1
    PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx);
    synchronized (head) {
        //allocate a new run
        // #2
        int runSize = calculateRunSize(sizeIdx);
        //runSize must be multiples of pageSize
        // #3
        long runHandle = allocateRun(runSize);
        if (runHandle < 0) {
            return -1;
        }
        // #4
        int runOffset = runOffset(runHandle);
        int elemSize = arena.sizeIdx2size(sizeIdx);
        PoolSubpage<T> subpage = new PoolSubpage<T>(head, this, pageShifts, runOffset,
                           runSize(pageShifts, runHandle), elemSize);
        subpages[runOffset] = subpage;
        // #5
        return subpage.allocate();
    }
}
#1 这里涉及修改PoolArena#smallSubpagePools中的PoolSubpage链表,需要同步操作
#2 计算内存块size和pageSize最小公倍数
#3 分配一个Normal内存块,作为PoolSubpage的底层内存块,大小为Small内存块size和pageSize最小公倍数
#4 构建PoolSubpage
runOffset,即Normal内存块偏移量,也是该PoolSubpage在整个Chunk中的偏移量
elemSize,Small内存块size
#5 在subpage上分配内存块
PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int pageShifts, int runOffset, int runSize, int elemSize) {
    // #1
    this.chunk = chunk;
    this.pageShifts = pageShifts;
    this.runOffset = runOffset;
    this.runSize = runSize;
    this.elemSize = elemSize;
    bitmap = new long[runSize >>> 6 + LOG2_QUANTUM]; // runSize / 64 / QUANTUM
    init(head, elemSize);
}
void init(PoolSubpage<T> head, int elemSize) {
    doNotDestroy = true;
    if (elemSize != 0) {
        // #2
        maxNumElems = numAvail = runSize / elemSize;
        nextAvail = 0;
        bitmapLength = maxNumElems >>> 6;
        if ((maxNumElems & 63) != 0) {
            bitmapLength ++;
        }
        for (int i = 0; i < bitmapLength; i ++) {
            bitmap[i] = 0;
        }
    }
    // #3
    addToPool(head);
}
#1 bitmap长度为runSize / 64 / QUANTUM,从《内存对齐类SizeClasses》可以看到,runSize都是2^LOG2_QUANTUM的倍数。
#2
elemSize:每个内存块的大小
maxNumElems:内存块数量
bitmapLength:bitmap使用的long元素个数,使用bitmap中一部分元素足以管理全部内存块。
(maxNumElems & 63) != 0,代表maxNumElems不能整除64,所以bitmapLength要加1,用于管理余下的内存块。
#3 添加到PoolSubpage链表中
前面分析《Netty内存池与PoolArena》中说过,在PoolArena中分配Small内存块时,首先会从PoolArena#smallSubpagePools中查找对应的PoolSubpage。如果找到了,直接从该PoolSubpage上分配内存。否则,分配一个Normal内存块,创建PoolSubpage,再在上面分配内存块。
PoolSubpage#allocate
long allocate() {
    // #1
    if (numAvail == 0 || !doNotDestroy) {
        return -1;
    }
    // #2
    final int bitmapIdx = getNextAvail();
    // #3
    int q = bitmapIdx >>> 6;
    int r = bitmapIdx & 63;
    assert (bitmap[q] >>> r & 1) == 0;
    bitmap[q] |= 1L << r;
    // #4
    if (-- numAvail == 0) {
        removeFromPool();
    }
    // #5
    return toHandle(bitmapIdx);
}
#1 没有可用内存块,分配失败。通常PoolSubpage分配完成后会从PoolArena#smallSubpagePools中移除,不再在该PoolSubpage上分配内存,所以一般不会出现这种场景。
#2 获取下一个可用内存块的bit下标
#3 设置对应bit为1,即已使用
bitmapIdx >>> 6,获取该内存块在bitmap数组中第q元素
bitmapIdx & 63,获取该内存块是bitmap数组中第q个元素的第r个bit位
bitmap[q] |= 1L << r,将bitmap数组中第q个元素的第r个bit位设置为1,表示已经使用
#4 所有内存块已分配了,则将其从PoolArena中移除。
#5 toHandle 转换为最终的handle
private int getNextAvail() {
    int nextAvail = this.nextAvail;
    if (nextAvail >= 0) {
        this.nextAvail = -1;
        return nextAvail;
    }
    return findNextAvail();
}
nextAvail为初始值或free时释放的值。
如果nextAvail存在,设置为不可用后直接返回该值。
如果不存在,调用findNextAvail查找下一个可用内存块。
private int findNextAvail() {
    final long[] bitmap = this.bitmap;
    final int bitmapLength = this.bitmapLength;
    // #1
    for (int i = 0; i < bitmapLength; i ++) {
        long bits = bitmap[i];
        if (~bits != 0) {
            return findNextAvail0(i, bits);
        }
    }
    return -1;
}
private int findNextAvail0(int i, long bits) {
    final int maxNumElems = this.maxNumElems;
    final int baseVal = i << 6;
    // #2
    for (int j = 0; j < 64; j ++) {
        if ((bits & 1) == 0) {
            int val = baseVal | j;
            if (val < maxNumElems) {
                return val;
            } else {
                break;
            }
        }
        bits >>>= 1;
    }
    return -1;
}
#1 遍历bitmap,~bits != 0,表示存在一个bit位不为1,即存在可用内存块。
#2 遍历64个bit位,
(bits & 1) == 0,检查最低bit位是否为0(可用),为0则返回val。
val等于 (i << 6) | j,即i * 64 + j,该bit位在bitmap中是第几个bit位。
bits >>>= 1,右移一位,处理下一个bit位。
内存释放
释放Small内存块可能有两个步骤
- 释放PoolSubpage的上内存块
- 如果PoolSubpage中的内存块已全部释放,则从Chunk中释放该PoolSubpage,同时从PoolArena#smallSubpagePools移除它。
PoolSubpage#free
boolean free(PoolSubpage<T> head, int bitmapIdx) {
    if (elemSize == 0) {
        return true;
    }
    // #1
    int q = bitmapIdx >>> 6;
    int r = bitmapIdx & 63;
    assert (bitmap[q] >>> r & 1) != 0;
    bitmap[q] ^= 1L << r;
    setNextAvail(bitmapIdx);
    // #2
    if (numAvail ++ == 0) {
        addToPool(head);
        return true;
    }
    // #3
    if (numAvail != maxNumElems) {
        return true;
    } else {
        // #4
        if (prev == next) {
            // Do not remove if this subpage is the only one left in the pool.
            return true;
        }
        // #5
        doNotDestroy = false;
        removeFromPool();
        return false;
    }
}
#1 将对应bit位设置为可以使用
#2 在PoolSubpage的内存块全部被使用时,释放了某个内存块,这时重新加入到PoolArena中。
#3 未完全释放,即还存在已分配内存块,返回true
#4 逻辑到这里,是处理所有内存块已经完全释放的场景。
PoolArena#smallSubpagePools链表组成双向链表,链表中只有head和当前PoolSubpage时,当前PoolSubpage的prev,next都指向head。
这时当前PoolSubpage是PoolArena中该链表最后一个PoolSubpage,不释放该PoolSubpage,以便下次申请内存时直接从该PoolSubpage上分配。
#5 从PoolArena中移除,并返回false,这时PoolChunk会将释放对应Page节点。
void free(long handle, int normCapacity, ByteBuffer nioBuffer) {
    if (isSubpage(handle)) {
        // #1
        int sizeIdx = arena.size2SizeIdx(normCapacity);
        PoolSubpage<T> head = arena.findSubpagePoolHead(sizeIdx);
        PoolSubpage<T> subpage = subpages[runOffset(handle)];
        assert subpage != null && subpage.doNotDestroy;
        synchronized (head) {
            // #2
            if (subpage.free(head, bitmapIdx(handle))) {
                //the subpage is still used, do not free it
                return;
            }
        }
    }
    // #3
    ...
}
#1
查找head节点,同步
#2
调用subpage#free释放Small内存块
如果subpage#free返回false,将继续向下执行,这时会释放PoolSubpage整个内存块,否则,不释放PoolSubpage内存块。
#3 释放Normal内存块,就是释放PoolSubpage整个内存块。该部分内容可参考《PoolChunk实现原理》。
如果您觉得本文不错,欢迎关注我的微信公众号,系列文章持续更新中。您的关注是我坚持的动力!

Netty源码解析 -- PoolSubpage实现原理的更多相关文章
- Netty源码解析 -- PoolChunk实现原理
		本文主要分享Netty中PoolChunk如何管理内存. 源码分析基于Netty 4.1.52 内存管理算法 首先说明PoolChunk内存组织方式. PoolChunk的内存大小默认是16M,Net ... 
- Netty源码解析 -- PoolChunk实现原理(jemalloc 3的算法)
		前面文章已经分享了Netty如何实现jemalloc 4算法管理内存. 本文主要分享Netty 4.1.52之前版本中,PoolChunk如何使用jemalloc 3算法管理内存. 感兴趣的同学可以对 ... 
- 顺序线性表 ---- ArrayList 源码解析及实现原理分析
		原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ... 
- Netty源码解析—客户端启动
		Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ... 
- Netty源码解析---服务端启动
		Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ... 
- Netty 源码解析(三): Netty 的 Future 和 Promise
		今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ... 
- Netty 源码解析(九): connect 过程和 bind 过程分析
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ... 
- Netty 源码解析(八): 回到 Channel 的 register 操作
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ... 
- Netty 源码解析(七): NioEventLoop 工作流程
		原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ... 
随机推荐
- 5w 字 | 172 图 | 超级赛亚级 Spring Cloud 实战
			一.PassJava 项目简介 PassJava-Learning 项目是 PassJava(佳必过)项目的学习教程.对架构.业务.技术要点进行讲解. PassJava 是一款 Java 面试刷题 的 ... 
- Grakn Forces 2020 ABCDE题解
			看到老外评论区中说,这场的难度估计是\(div.1\)和\(div.1.5\)的合并 A. Circle Coloring #构造 题目链接 题意 给定三个长度为\(n\)数组\(a,b,c\),要你 ... 
- SpringBoot 整合邮件oh-my-email 实现发送邮件功能
			导读 最近手头上要负责整个Message Gateway服务的搭建,涉及到:微信推送(点我直达).短信.邮件等等,到github上发现有个微型的开源邮件框架,整理下来,以备项目中使用到,到时候应该会使 ... 
- java面试复习重点:类的管理及常用工具,教你抓住面试的重点!
			java复习: 类的管理及常用工具类 包 写在程序文件的第一行 一个Java 源文件中只能声明一个包, 且声明语句只能作为源文件的第一条指令 导入类能导入非public类,但是不能用因为在其他包缺省的 ... 
- linux查看内存及磁盘使用情况
			1.查看当前目录 命令: df -h (统一每个目录下磁盘的整体情况) 2.查看指定目录 在命令后直接放目录名,比如查看"usr"目录使用情况: 命令: df ... 
- SkyWalking —— 分布式应用监控与链路追踪
			SkyWalking 是一个应用性能监控系统,特别为微服务.云原生和基于容器(Docker, Kubernetes, Mesos)体系结构而设计.除了应用指标监控以外,它还能对分布式调用链路进行追踪. ... 
- 010 editor的使用
			原文链接:http://www.cnblogs.com/vendanner/p/4939444.html 注意事项:之前一直在虚拟机winxp中添加template一直失败,原因可能是因为虚拟机的版本 ... 
- 3、Spring Cloud Rest工程创建(通过IDEA创建)
			1.Rest微服务构建简介 (1).介绍 以Dept部门模块做一个微服务通用案例Consumer消费者(Client)通过REST调用Provider提供者(Server)提供的服务. (2).Myb ... 
- java课后作业2019.11.04
			一.编写一个程序,指定一个文件夹,能够自动计算出其总容量 1.代码 package HomeWork; import java.io.File; public class getFileDaxiao ... 
- Python接口测试-使用requests模块发送GET请求
			本篇主要记录下使用python的requests模块发送GET请求的实现代码. 向服务器发送get请求:无参数时:r = requests.get(url)带params时:r = requests. ... 
