Netty的内存池的实现有些复杂,使用了大量的位运算,晦涩难懂,不过万能的博客上好多大神已经介绍的非常详细,推荐四篇很详细很棒的源码分析的文章链接,本文根据自己的理解顺一下思路,内容主要也是出自以下四篇:

Netty的内存池整体上参照jemalloc实现,组成部分主要包括PoolArena、PoolChunkList、PoolChunk、PoolPage、PoolSubpage这5个层级,基本关系盗张图展示下:(图出自Netty内存池实现)

在这里插入图片描述

ByteBuf申请的入口在PooledByteBufAllocator,PooledByteBufAllocator创建的时候,会创建两个Arean(一个heap和一个direct)数组,这个数组里的arena会分配给每个线程(意思就是每个线程有两个arena)。

在这里插入图片描述

Netty会为每一个线程都维护一个PoolThreadCache对象,这个PoolThreadLocalCache,调用get的时候,会得到当前线程的PoolThreadCache,如果不存在则初始化一个,初始化时首先会查找PoolArena数组中被最少线程占用的那个arena,然后将其封装到一个新建的PoolThreadCache中返回,通过当前线程的这个PoolThreadCache 就能读取到directArena(直接内存),然后就在这个directArena这里开始分配内存了。但是最终的内存分配工作被委托给PoolArena。

当进行内存申请时,首先会尝试从PoolThreadCache中申请,如果无法从中申请到,则会尝试从Netty的公共内存池中申请。PoolThreadCache申请内存并不是说其会创建一块内存,或者说其会到PoolArena中申请内存,而是指,其本身已经缓存有内存块,而当前申请的内存块大小正好与其一致,就会将该内存块返回;PoolThreadCache中的内存块都是在当前线程使用完创建的ByteBuf对象后,通过调用其release()方法释放内存时直接缓存到当前PoolThreadCache中的,其并不会直接将内存块返回给PoolArena,PoolThreadCache会对其内存块使用次数进行计数,这么做的目的在于,如果一个ThreadPoolCache所缓存的内存块使用较少,那么就可以将其释放到PoolArena中,以便于其他线程可以申请使用。

PoolArena

真是的内存分配工作会在PoolArena中进行,PoolArena中主要包含三部分子内存池:tinySubpagePools,smallSubpagePools和一系列按内存使用率划分的的PoolChunkList。初始状态时,tinySubpagePools是一个长度为32的数组,smallSubpagePools是一个长度为4的数组,PoolChunkList为空,几个PoolChunkList会串成一个单向链表,而PoolChunkList中的PoolChunk也是以单向链表存储,PoolChunk会根据内存占用率的不同而在几个PoolChunkList中依次传递。

内部参数如下:

在这里插入图片描述

内存分配部分代码如下:

在这里插入图片描述

首先判断内存的大小,决定分配tiny,small还是normal内存。然后会尝试从当前线程的缓存PoolThreadCache中申请目标内存,如果能够申请到,则直接返回,如果不能申请到,则在当前层级中申请。对于tiny和small层级的内存申请,如果无法申请到,则会在PoolChunkList中申请,如果还没申请到,就创建一个PoolChunk申请,并执行PoolChunk#allocate方法申请,申请到之后还会判断PoolChunk的使用率决定它在哪个PoolChunkList中。

PoolChunk

一个Page的默认大小是8K,一个Chunk中默认包含了2048个Page,共16M,以2048个page为叶子节点,构成了一个一颗深度为11的满二叉树,共4095个节点,每个节点的代表的内存空间是其两个儿子节点代表内存空间的和,PoolChunk中维护了的memoryMap和depthMap两个数组,每个数组长度为4096,第一个位置存了0,后续4095个位置按层序顺遍历顺序存了所有节点的所在的层数,depthMap一样,每次数字代表了可分配的内存,比如0就是16M,11就是8k等。如果某个子节点被分配完了,那么该节点就会被标记为12(最大深度+1),表示已分配,同时递归更新其父节点,同步减少其父节点可供分配内存的值。PoolChunk只负责小于16M的内存分配,对于大于16M的内存分配不走池化。

PoolChunk对象属性如下:

在这里插入图片描述

上述PoolArena如果申请不到内存会新建PoolChunk,新建并初始化PoolChunk会正式申请一块内存(此处通过PlatformDependent根据指定回收策略申请一块堆外内存),然后执行PoolChunk#allocate方法做memoryMap调整以及ByteBuffer初始化等工作。

PoolChunk#allocate中对目标容量做了判断,小于8KB的走allocateSubpage方法,大于8KB走allocateRun方法,两个方法会返回一个handle,里边存储的高32位是PoolSubpage内存块的位图索引(下文介绍),低32位是PoolChunk的内存占用索引(下文介绍),代码如下。

在这里插入图片描述

在allocateRun中调用了allocateNode方法,主要实现了上文中描述的对memoryMap的维护,因为不需要使用PoolSubpage,所以返回的handler中高32位为0,只把memoryMap返回即可。

在这里插入图片描述

在这里插入图片描述

完成后上述操作后,将PoolChunk放入对应的PoolChunkList中,然后以申请到的内存数据为参数完成对应的ByteBuf的初始化操作,各种返回就得到了申请的池化的ByteBuf。

PoolSubpage

上文简单描述了大于8K的内存分配流程,小于8K的内存有PoolSubpage维护(虽说是小于8K,但当申请的内存大于4096B,netty就会将其扩容为8K分配normal内存),分配主要分为两种情况:小于512B的由tinySubpagePools的PoolSubpage的数组维护,tinySubpagePools数组大小为32,以数组加链表的形式存储,每个数组元素从小到大,均未16的倍数,代表其后面链表中的PoolSubpage的内存划分大小,比如按16B划分的内存块链到数组1号元素后边,32B划分的链到2后边,以此类推,最大为196;大于等于512B的则由smallSubpagePools的PoolSubpage数组来维护,结构与tinySubpagePools一样,数组只有四个元素,第一个512B,后边的每个都翻倍。

上文描述一个Page的大小为8K,PoolSubpage操作的节点是在叶子节点上划分内存块,所以PoolSubpage引入了bitmap来表示所划分的内存块是否被占用。由于内存划分最小为16B,8K内存最大可分为512个内存块,所以至少要512位来标记,Netty中使用了8个long组成的数组来表示内存块占用情况,一个long是64为,8个就是512位,即bitmap。但如果按其他内存大小划分,内存块个数要远小于512,所以用bitmapLength标识有几个long标记内存块。

PoolSubpage对象属性如下:

在这里插入图片描述

回到PoolChun#allocate方法,当申请的内存小于8K的时候,进入allocateSubpage方法。

在这里插入图片描述

此时依旧会确定memoryMap中的位置以及标记占用,不过是直接在叶子节点中寻找。然后获取一个PoolSubpage对象,如果没有,就创建一个,对其对象进行初始化操作(初始化位图索引等的初始值,并将对象放入其对应链表中等操作),然后调用其allocate方法,获得其位图索引。

在这里插入图片描述

返回的handler中高32位表示bitmap索引,但并不是真正的bitmap,只是低六位记录了在long元素的第几位,7~9位记录了是数组中的几号long元素,然后将得到的int数据移到高32位,将memoryMap放到低32位,组成handler返回,然后就是后续的Bytebuf初始化操作。

内存回收

内存回收依旧是release方法,判断引用计数器是否需要回收内存,最终调用的是PooledByteBuf中实现的deallocate方法。

在这里插入图片描述

调用PoolArena#free方法

在这里插入图片描述

非池化直接销毁内存块,池化判断要回收的类型,尝试将其释放到PoolThreadCache中,上文申请内存的时候,尝试从缓存中获取内存即是此时回收的内存。

在这里插入图片描述

根据内存分配的类型获得对应的MemoryRegionCache,然后将内存块释放到里面。

如果线程缓存已满,则将目标内存返回到公共内存块中,最终会执行PoolChunk#free方法。

在这里插入图片描述

所做工作是重置位图索引,将ByteBuf放入缓存池,如果是PoolSubpage对象,则调用PoolSubpage#free,代码如下:

在这里插入图片描述

也是重置位图索引,调整链表的操作。

至此,一次池化的内存分配流程就有了简单地介绍。

bytebuf池_Netty篇:ByteBuf之内存池源码分析相关推荐

  1. Java Review - 线程池资源一直不被释放案例源码分析

    文章目录 概述 问题复现 源码分析 小结 概述 在日常开发中为了便于线程的有效复用,经常会用到线程池,然而使用完线程池后如果不调用shutdown关闭线程池,则会导致线程池资源一直不被释放. 下面通过 ...

  2. AI作曲基础-Python编程作曲软件篇-FoxDot文档及源码分析-官方教程01

    AI作曲基础-Python编程作曲软件篇-FoxDot文档及源码分析-官方教程01 前言 本系列系列目录放在文尾: 本系列是AI作曲的基础,暂时和AI关系不大,但尤为重要: 借助FoxDot,从文档分 ...

  3. 《微信小程序-进阶篇》Lin-ui组件库源码分析-列表组件List(一)

    大家好,这是小程序系列的第二十篇文章,在这一个阶段,我们的目标是 由简单入手,逐渐的可以较为深入的了解组件化开发,从本文开始,将记录分享lin-ui的源码分析,期望通过对lin-ui源码的学习能加深组 ...

  4. 字符设备驱动基础篇1——简单的驱动源码分析

    以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除. 参考资料:http://www.cnblogs.com/biaohc/p/6575074.html module_test.c代码 #inc ...

  5. Android 匿名共享内存驱动源码分析

    原址 Android系统的匿名共享内存Ashmem驱动程序利用了Linux的共享内存子系统导出的接口来实现,本文通过源码分析方式详细介绍Android系统的匿名共享内存机制.在Android系统中,匿 ...

  6. java刷卡计时计次源码美萍_Java 定时调配 Timer 类和定任务 TimerTask 类(一篇详细且完整的源码分析以及四种简单的使用方法)...

    前言 在我们日常生活中,我们常常会遇到有关计时器的事情.如商城类项目会在某年某月某日某时某分某秒进行特价活动,那么当时间到达这个时间点上的时候该事件就会触发. 1.Timer 类构造函数摘要 1 Ti ...

  7. java 线程池 源码_java线程池源码分析

    我们在关闭线程池的时候会使用shutdown()和shutdownNow(),那么问题来了: 这两个方法又什么区别呢? 他们背后的原理是什么呢? 线程池中线程超过了coresize后会怎么操作呢? 为 ...

  8. Spring 源码分析衍生篇十 :Last-Modified 缓存机制

    文章目录 一.前言 二.Last-Modify 三.实现方案 1. 实现 org.springframework.web.servlet.mvc.LastModified接口 1.1. 简单演示 1. ...

  9. Netty技术细节源码分析-ByteBuf的内存泄漏原因与检测

    本文的github地址:点此 该文所涉及的netty源码版本为4.1.6. Netty中的ByteBuf为什么会发生内存泄漏 在Netty中,ByetBuf并不是只采用可达性分析来对ByteBuf底层 ...

最新文章

  1. 剑指offer系列之十:二进制中1的个数
  2. 若依 v4.6.1 后台 排除log4j
  3. bgp选路原则【第二部】
  4. 学会python编程容易吗-学习武汉Python编程培训容易吗?别人都是怎么学习的?
  5. Linux使用imagemagick的convert命令压缩图片、节省服务器空间
  6. Nginx负载均衡策略有哪些?知识点总结+面试题解析
  7. mysql根据idb还原数据_mysql通过idb文件,恢复数据库
  8. 谈均值、方差、标准差、协方差的概念及意义
  9. Hadoop namenode无法启动问题解决
  10. WIN32汇编 菜单和加速键的使用
  11. 【解决方法】 Flash cs4 安装之后打不开 启动界面总是一闪而过
  12. java案例2-6:登录注册
  13. cadz轴归零命令_cadz轴归零(cad全部z轴归零)
  14. SVN-----CornerStone
  15. cad动态块制作翻转_cad动态块制作教程
  16. IT十年人生过客-七-眉毛与恶名
  17. 程序员英语再渣也要会的单词
  18. DNS域名解析和正向解析
  19. 位、比特(bit)、字节(Byte)区别与关系
  20. 步进电机编码器闭环程序,西门子200PLC和威纶通触摸屏实控制步进电机

热门文章

  1. 写轮播图时,关于offsetX和pageX的选择以及一些坑
  2. Apache Geode 2.11 运行Geode服务器进程
  3. webrtc拥塞控制
  4. 华为OD机试 -分苹果(JavaScript) | 机试题+算法思路+考点+代码解析 【2023】
  5. 离散数学1:数理逻辑
  6. RationalDMIS 2020 半圆弧/圆柱测量的测量方法
  7. scoped scoped穿透
  8. 游戏手柄 GameController
  9. c语言小游戏 三子棋,C语言实现简单的三子棋小游戏
  10. 这个被微软雪藏十几年的官方插件,没想到原来这么好用