关键概念
PoolArena——内存管理的统筹者
PoolArena是内存管理的统筹者。它内部有一个PoolChunkList组成的链表
PoolChunkList——对PoolChunk的管理
PoolChunkList内部有一个PoolChunk组成的链表。通常一个PoolChunkList中的所有PoolChunk使用率(已分配内存/ChunkSize)都在相同的范围内。
每个PoolChunkList有自己的最小使用率或者最大使用率的范围,PoolChunkList与PoolChunkList之间又会形成链表,并且使用率范围小的PoolChunkList会在链表中更加靠前。
PoolChunck ——Netty向OS申请的最小内存
为了减少频繁的向操作系统申请内存的情况,Netty会一次性申请一块较大的内存,其大小由ChunkSize决定(默认为16M,即一次向OS申请16M的内存)。而后对这块内存进行管理,每次按需将其中的一部分分配给内存使用者(即ByteBuf)。
Page——PoolChunck所管理的最小内存单位
PoolChunk所能管理的最小内存叫做Page,大小由PageSize(默认为8K),即一次向PoolChunk申请的内存都要以Page为单位(一个或多个Page)。
PoolSubpage——小内存的管理者
PoolChunk管理的最小内存是一个Page(默认8K),而当我们需要的内存比较小时,直接分配一个Page无疑会造成内存浪费。
PoolSubPage就是用来管理这类细小内存的管理者。
PoolThreadCache——线程本地缓存,减少内存分配时的竞争
PoolArena免不了产生竞争,Netty除了创建多个PoolArena减少竞争外,还让线程在释放内存时缓存已经申请过的内存,而不立即归还给PoolArena。
缓存的内存被存放在PoolThreadCache内,它是一个线程本地变量,因此是线程安全的,对它的访问也不需要上锁。
MemoryRegionCache
一个内部队列,同一队列内的所有节点可以看成是该线程使用过的同一规格的内存块。同时,它还有个size属性控制队列过长(队列满后,将不在缓存该规格的内存块,而是直接还给PoolArena)
PoolChunck如何管理Page
首先PoolChunk通过一个完全二叉树来组织内部的内存。以默认的ChunkSize为16M, PageSize为8K为例,一个PoolChunk可以划分成2048个Page。将这2048个Page看作是叶子节点的宽度,可以得到一棵深度为11的树(2^11=2048)。
让每个叶子节点管理一个Page,那么其父节点管理的内存即为两个Page(其父节点有左右两个叶子节点),以此类推,树的根节点管理了这个PoolChunk所有的Page(因为所有的叶子结点都是其子节点),而树中某个节点所管理的内存大小即是以该节点作为根的子树所包含的叶子节点管理的全部Page。
重头梳理一遍内存申请的过程:
PooledByteBufAllocator.newHeapBuffer()开始申请内存
获取线程本地的变量PoolThreadCache以及和线程绑定的PoolArena
通过PoolArena分配内存,先获取ByteBuf对象(可能是对象池回收的也可能是创建的),在开始内存分配
分配前先判断此次内存的等级,尝试从PoolThreadCache的找相同规格的缓存内存块使用,没有则从PoolArena中分配内存
对于Normal等级内存而言,从PoolChunkList的链表中找合适的PoolChunk来分配内存,如果没有则先像OS申请一个PoolChunk,在由PoolChunk分配相应的Page
对于Tiny和Small等级的内存而言,从对应的PoolSubpage缓存池中找内存分配,如果没有PoolSubpage,线会到第5步,先分配PoolChunk,再由PoolChunk分配Page给PoolSubpage使用
对于Huge等级的内存而言,不会缓存,会在用的时候申请,释放的时候直接回收
8.将得到的内存给ByteBuf使用,就完成了一次内存申请的过程
参考
https://www.cnblogs.com/insaneXs/p/13726158.html