前面文章已经分享了Netty如何实现jemalloc 4算法管理内存。

本文主要分享Netty 4.1.52之前版本中,PoolChunk如何使用jemalloc 3算法管理内存。

感兴趣的同学可以对比两种算法。

源码分析基于Netty 4.1.29

首先说明PoolChunk内存组织方式。

PoolChunk的内存大小默认是16M,它将内存组织成为一颗完美二叉树。

二叉树的每一层每个节点所代表的内存大小都是均等的,并且每一层节点所代表的内存大小总和加起来都是16M。

每一层节点可分配内存是父节点的1/2。整颗二叉树的总层数为12,层数从0开始。

示意图如下

先看一下PoolChunk的构造函数

PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize, int offset) {
unpooled = false;
this.arena = arena;
this.memory = memory;
this.pageSize = pageSize;
this.pageShifts = pageShifts;
this.maxOrder = maxOrder;
this.chunkSize = chunkSize;
this.offset = offset;
unusable = (byte) (maxOrder + 1);
log2ChunkSize = log2(chunkSize);
subpageOverflowMask = ~(pageSize - 1);
freeBytes = chunkSize; assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
maxSubpageAllocs = 1 << maxOrder; // Generate the memory map.
memoryMap = new byte[maxSubpageAllocs << 1];
depthMap = new byte[memoryMap.length];
int memoryMapIndex = 1;
for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
int depth = 1 << d;
for (int p = 0; p < depth; ++ p) {
// in each level traverse left to right and set value to the depth of subtree
memoryMap[memoryMapIndex] = (byte) d;
depthMap[memoryMapIndex] = (byte) d;
memoryMapIndex ++;
}
} subpages = newSubpageArray(maxSubpageAllocs);
}

unpooled: 是否使用内存池

arena:该PoolChunk所属的PoolArena

memory:底层的内存块,对于堆内存,它是一个byte数组,对于直接内存,它是(jvm)ByteBuffer,但无论是哪种形式,其内存大小默认都是16M。

pageSize:叶子节点大小,默认为8192,即8K。

maxOrder:表示二叉树最大的层数,从0开始。默认为11。

chunkSize:整个PoolChunk的内存大小,默认为16777216,即16M。

offset:底层内存对齐偏移量,默认为0。

unusable:表示节点已被分配,不用了,默认为12。

freeBytes:空闲内存字节数。

每个PoolChunk都要按内存使用率关联到一个PoolChunkList上,内存使用率正是通过freeBytes计算。

maxSubpageAllocs:叶子节点数量,默认为2048,即2^11。

log2ChunkSize:用于计算偏移量,默认为24。

subpageOverflowMask:用于判断申请内存是否为PoolSubpage,默认为-8192。

pageShifts:用于计算分配内存所在二叉树层数,默认为13。

memoryMap:初始化内存管理二叉树,将每一层节点值设置为层数d。

使用数组维护二叉树,第d层的开始下标为 1<<d。(数组第0个元素不使用)。

depthMap:保存二叉树的层数,用于通过位置下标找到其在整棵树中对应的层数。

注意:depthMap的值代表二叉树的层数,初始化后不再变化。

memoryMap的值代表当前节点最大可申请内存块,在分配内存过程中不断变化。

节点最大可申请内存块可以通过层数d计算,为2 ^ (pageShifts + maxOrder - d)

PoolChunk#allocate

long allocate(int normCapacity) {
if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
return allocateRun(normCapacity);
} else {
return allocateSubpage(normCapacity);
}
}

若申请内存大于pageSize,调用allocateRun方法分配Chunk级别的内存。

否则调用allocateSubpage方法分配PoolSubpage,再在PoolSubpage上分配所需内存。

PoolChunk#allocateRun

private long allocateRun(int normCapacity) {
// #1
int d = maxOrder - (log2(normCapacity) - pageShifts);
// #2
int id = allocateNode(d);
if (id < 0) {
return id;
}
// #2
freeBytes -= runLength(id);
return id;
}

#1 计算应该在哪层分配分配内存

maxOrder - (log2(normCapacity) - pageShifts),如16K, 即2^14,计算结果为10,即在10层分配。

#2 减少空闲内存字节数。

PoolChunk#allocateNode,在d层分配一个节点

private int allocateNode(int d) {
int id = 1;
int initial = - (1 << d); // has last d bits = 0 and rest all = 1
// #1
byte val = value(id);
if (val > d) { // unusable
return -1;
}
// #2
while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
// #3
id <<= 1;
val = value(id);
// #4
if (val > d) {
// #5
id ^= 1;
val = value(id);
}
}
byte value = value(id);
assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
value, id & initial, d);
// #6
setValue(id, unusable); // mark as unusable
// #7
updateParentsAlloc(id);
return id;
}

#1 memoryMap[1] > d,第0层的可分配内存不足,表明该PoolChunk内存不能满足分配,分配失败。

#2 遍历二叉树,找到满足内存分配的节点。

val < d,即该节点内存满足分配。

id & initial = 0,即 id < 1<<d, d层之前循环继续执行。这里并不会出现val > d的场景,但会出现val == d的场景,如

PoolChunk当前可分配内存为2M,即memoryMap[1] = 3,这时申请2M内存,在0-2层,都是val == d。可参考后面的实例。

#3 向下找到下一层下标,注意,子树左节点的下标是父节点下标的2倍。

#4 val > d,表示当前节点不能满足分配

#5 id ^= 1,查找同一父节点下的兄弟节点,在兄弟节点上分配内存。

id ^= 1,当id为偶数,即为id+=1, 当id为奇数,即为id-=1

由于前面通过id <<= 1找到下一层下标都是偶数,这里等于id+=1。

#6

因为一开始判断了PoolChunk内存是否足以分配,所以这里一定可以找到一个可分配节点。

这里标注找到的节点已分配。

#7 更新找到节点的父节点最大可分配内存块大小

private void updateParentsAlloc(int id) {
// #1
while (id > 1) {
// #2
int parentId = id >>> 1;
byte val1 = value(id);
byte val2 = value(id ^ 1);
byte val = val1 < val2 ? val1 : val2;
setValue(parentId, val);
id = parentId;
}
}

#1 向父节点遍历,直到根节点

#2 id >>> 1,找到父节点

取当前节点和兄弟节点中较小值,作为父节点的值,表示父节点最大可分配内存块大小。

如memoryMap[1] = 0,表示最大可分配内存块为16M。

分配8M后,memoryMap[1] = 1,表示当前最大可分配内存块为8M。

下面看一则实例,大家可以结合实例理解上面的代码

内存释放

PoolChunk#free

void free(long handle) {
// #1
int memoryMapIdx = memoryMapIdx(handle);
int bitmapIdx = bitmapIdx(handle);
// #2
if (bitmapIdx != 0) { // free a subpage
...
}
freeBytes += runLength(memoryMapIdx);
setValue(memoryMapIdx, depth(memoryMapIdx));
updateParentsFree(memoryMapIdx);
}

#1 获取memoryMapIdx和bitmapIdx

#2 内存块在PoolSubpage中分配,通过PoolSubpage释放内存。

#3 处理到这里,就是释放Chunk级别的内存块了。

增加空闲内存字节数。

设置二叉树中对应的节点为未分配

对应修改该节点的父节点。

另外,Netty 4.1.52对PoolArena内存级别划分的算法也做了调整。

Netty 4.1.52的具体算法前面文章《Netty内存池与PoolArena》已经说过了,这里简单说一下Netty 4.1.52前的算法。

PoolArena中将维护的内存块按大小划分为以下级别:

Tiny < 512

Small < 8192(8K)

Chunk < 16777216(16M)

Huge >= 16777216

PoolArena#tinySubpagePools,smallSubpagePools两个数组用于维护Tiny,Small级别的内存块。

tinySubpagePools,32个元素,每个数组之间差16个字节,大小分别为0,16,32,48,64, ... ,496

smallSubpagePools,4个元素,每个数组之间大小翻倍,大小分别为512,1025,2048,4096

这两个数组都是PoolSubpage数组,PoolSubpage大小默认都是8192,Tiny,Small级别的内存都是在PoolSubpage上分配的。

Chunk内存块则都是8192的倍数。

在Netty 4.1.52,已经删除了Small级别内存块,并引入了SizeClasses对齐内存块或计算对应的索引。

SizeClasses默认将16M划分为75个内存块size,内存划分更细,也可以减少内存对齐的空间浪费,更充分利用内存。感兴趣的同学可以参考前面的文章《内存对齐类SizeClasses》。

如果您觉得本文不错,欢迎关注我的微信公众号,系列文章持续更新中。您的关注是我坚持的动力!

Netty源码解析 -- PoolChunk实现原理(jemalloc 3的算法)的更多相关文章

  1. Netty源码解析 -- PoolChunk实现原理

    本文主要分享Netty中PoolChunk如何管理内存. 源码分析基于Netty 4.1.52 内存管理算法 首先说明PoolChunk内存组织方式. PoolChunk的内存大小默认是16M,Net ...

  2. Netty源码解析 -- PoolSubpage实现原理

    前面文章说了PoolChunk如何管理Normal内存块,本文分享PoolSubpage如何管理Small内存块. 源码分析基于Netty 4.1.52 内存管理算法 PoolSubpage负责管理S ...

  3. 顺序线性表 ---- ArrayList 源码解析及实现原理分析

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...

  4. Netty源码解析—客户端启动

    Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ...

  5. Netty源码解析---服务端启动

    Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...

  6. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

  7. Netty 源码解析(九): connect 过程和 bind 过程分析

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  8. Netty 源码解析(八): 回到 Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  9. Netty 源码解析(七): NioEventLoop 工作流程

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

随机推荐

  1. centos xargs

    有些时候过滤后的东西需要传递给后面其它命令执行实现需求,这个时候xargs就派上用场了. 比如我想把过滤后的东东拷贝至其它目录,其实我可以进入那个目录然后执行ls,然后是过滤,接着再手工以拷贝那样也可 ...

  2. EggJS 云原生应用硬核实战(Kubernetes+Traefik+Helm+Prometheus+Grafana),提供 Demo

    介绍 这是一个关于 Egg.js 应用上云️的示例,笔者所在的大前端团队的已应用于生产. CI/CD & DevOps & GitOps & HPA 等这里暂不做讨论,因为每一 ...

  3. 一种更优雅的Flutter Dialog解决方案

    前言 系统自带的Dialog实际上就是Push了一个新页面,这样存在很多好处,但是也存在一些很难解决的问题 必须传BuildContext loading弹窗一般都封装在网络框架中,多传个contex ...

  4. linux搭建harbor与使用

    条件:安装docker&docker-compose 如未安装,请看:linux离线安装docker + docker-compose harbor 1.下载 下载地址:https://git ...

  5. 第三十七章、PyQt输入部件:QAbstractSlider派生类QScrollBar滚动条、QSlider滑动条、QDial刻度盘功能介绍

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.引言 Designer中的输入部件Horizo ...

  6. 转:csdn怎么快速别人的文章

    在csdn看到好的文章想转载,无奈找不到转载的功能,只能想办法了. 首先确定原文允许转载 在文章开头处一般有版权声明,如图 转载时要注明出处和作者 如何转载 用谷歌浏览器加载文章地址,打开文章 F12 ...

  7. 第11.22节 Python 中re模块的字符串分割器:split函数

    一. 引言 在<第11.2节 Python 正则表达式支持函数概览>介绍了re模块的主要函数,在<第11.3节 Python正则表达式搜索支持函数search.match.fullm ...

  8. 第十九章、Model/View开发:QTableView的功能及属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 在Designer的部件栏Item Views中提供了PyQt和Qt已经实现好的table ...

  9. PyQt学习问题:Model/View中中EditKeyPressed常量平台编辑键(the platform edit key )是什么?

    老猿在学习PyQt的Model/View设计时,发现是否允许对视图中的数据项进行编辑的函数setEditTriggers的参数QAbstractItemView.EditTriggers是几个常量的组 ...

  10. PyQt(Python+Qt)学习随笔:Qt Designer中主窗口对象的toolButtonStyle属性

    tooButtonStyle属性保存主窗口工具栏按钮的样式设置,用来表示工具栏按钮的文字和图标怎么显示. 该属性的可设置值类型为枚举类型Qt.ToolButtonStyle,它包含如下值: 该属性的缺 ...