Netty内存池架构
2021-04-29 20:04:36 36 举报
Netty内存池里架构抽丝剥茧,交流学习
作者其他创作
大纲/内容
no
NormalSubPageDirectCaches 3种
从tinySubpagepools分配
MRCache
0B
PoolThreadCache
...
Subpage
80B
yes
huge 非池化分配
Queue 256个512b
null
2KB
从q075分配
有值?
TinySubPageHeapCaches 32种规格
page1
16B
page3
chunk!=null?
Arena
Chunk
1KB
1个Arena由两个PoolSubpage数组和多个ChunkList组成。两个PoolSubpage数组分别为tinySubpagePools和smallSubpagePools。多个ChunkList按照双向链表排列,每个ChunkList里包含多个Chunk,每个Chunk里包含多个Page(默认2048个),每个Page(默认大小为8k字节)由多个Subpage组成。每个Arena由如下几个ChunkList构成:PoolChunkList<T> qInit: 存储内存利用率0-25%的chunkPoolChunkList<T> q000:存储内存利用率1-50%的chunkPoolChunkList<T> q025:存储内存利用率25-75%的chunkPoolChunkList<T> q050:存储内存利用率50-100%的chunkPoolChunkList<T> q075:存储内存利用率75-100%的chunkPoolChunkList<T> q100: 存储内存利用率100%的chunk每个ChunkList里包含的Chunk数量会动态变化,随着 Chunk 中 Page 的不断分配和释放,会导致很多碎片内存段,大大增加了之后分配一段连续内存的失败率。针对这种情况,可以把内存使用率较大的 Chunk 放到PoolChunkList 链表更后面。每个Chunk里默认包含2048个Page。每个Page包含的Subpage的大小和个数由首次从该Page分配的内存大小决定,1个page默认大小为8k,如果首次在该page中需要分配1k字节,那么该page就被分为8个Subpage,每个Subpage大小为1k。
取到?
SmallSubPageHeapCaches 4种规格
Entry
32B
48B
计算容量在该chunk满二叉树位置
page2047
请求分配reqCapacity大小内存
线程和PoolThreadCache、Arena相等,默认核心线程数*2,避免竞争
归一化reqCapacity为16倍数或者2次幂normCapacity
从smallSubpagepools分配
page4
从q050分配
496B
PooledByteBuf
+ recyclerHandle:Recycler.Handle<PooledByteBuf<T>> //回收器+ chunk:PoolChunk<T> + memory:T //内存空间。具体什么样的数据,通过子类设置泛型。+ handle:int //表示page和subpage数据结构的下标,方便回收定位+ offset:int //memory相对开始位置的偏移量+ length:int //容量+ maxLength:int //占用 {@link #memory} 的大小+ cache:PoolThreadCache //缓存层对象+ allocator:ByteBufAllocator //ByteBuf 分配器对象+ tmpNioBuf:ByteBuffer
q075
NioEventLoop
Queue 512个32b
normCapacity<512
在该chunk选择一个可用page创建subpage
TinySubPagePools 32种
PoolArena 在分配( #allocate(...) )和释放( #free(...) )内存的过程中,无可避免会出现 synchronized 的身影。虽然锁的粒度不是很大,但是一个 PoolArena 如果被多个线程引用,会带来线程锁的同步和竞争,并且,如果在锁竞争的过程中,申请 Direct ByteBuffer ,那么带来的线程等待就可能是几百毫秒的时间。Arena代表1个内存区域,为了优化内存区域的并发访问,netty中内存池是由多个Arena组成的数组,分配时会每个线程按照轮询策略选择1个Arena进行内存分配。给每个线程引入其独有的 tcache 线程缓存。在释放已分配的内存块时,不放回到 Chunk 中,而是缓存到 tcache 中。在分配内存块时,优先从 tcache 获取。无法获取到,再从 Chunk 中分配。通过这样的方式,尽可能的避免多线程的同步和竞争。Netty内存池如图所示:
分配后根据使用率决定放到哪个chunklist
Page
qInit
64B
根据normCapacity放到TinySubPagePools或SmallSubPagePools
满二叉树表示page块是否已分配。
SmallSubPageDirectCaches 4种
二、Netty 内存管理实现
从MRCache队列头部取值
normCapacity>pagesize
q050
q025
32KB
512B
从q025分配
page2
page0
TinySubPageDirectCaches 32种规格
16KB
Netty内存池结构——抽丝剥茧
q100
从q100分配
DirectArena
一、Netty 为什么要实现内存管理?
PoolSubpage[]
8KB
TinySubPageDirectCaches定位MRCache
4KB
Queue 512个16b
找到可用page?
计算handle
Queue 64个8K
分配成功后把该chunk添加到链表中
normCapacity<pagesize
NormalSubPageDirectCaches定位MRCache
q000
normCapacity<chunk
PooledByteBufAllocator
从qinit分配
SmallSubPagePools 23种
找到可分配节点?
Netty 为什么要实现内存管理?在 Netty 中,IO 读写必定是非常频繁的操作,而考虑到更高效的网络传输性能,Direct ByteBuffer 必然是最合适的选择。但是 Direct ByteBuffer 的申请和释放是高成本的操作,那么进行池化管理,多次重用是比较有效的方式。但是,不同于一般于我们常见的对象池、连接池等池化的案例,ByteBuffer 是有大小一说。又但是,申请多大的 Direct ByteBuffer 进行池化又会是一个大问题,太大会浪费内存,太小又会出现频繁的扩容和内存复制!!!所以呢,就需要有一个合适的内存管理算法,解决高效分配内存的同时又解决内存碎片化的问题。C/C++ 和 java 中有个围城,城里的想出来,城外的想进去!这个围城就是自动内存管理!就内存管理而言,GC带给我们的价值是不言而喻的,不仅大大的降低了程序员的心智包袱, 而且,也极大的减少了内存管理带来的 Crash 困扰,为函数式编程(大量的临时对象)、脚本语言编程带来了春天。但是对于QPS 非常高,比如1M级,在这种情况下,在每次处理中即便产生1K的垃圾,都会导致频繁的GC产生。 在这种模式下, C/C++ 的手工回收机制,效率更高。Netty 4 引入了手工内存的模式,我觉得这是一大创新,这种模式甚至于会延展, 应用到 Cache 应用中。实际上,结合 JVM 的诸多优秀特性,如果用 Java 来实现一个 Redis 型 Cache、 或者 In-memory SQL Engine,或者是一个 Mongo DB,我觉得相比 C/C++ 而言,都要更简单很多。 实际上,JVM 也已经提供了打通这种技术的机制,就是 Direct Memory 和 Unsafe 对象。 基于这个基础,我们可以像 C 语言一样直接操作内存。实际上,Netty4 的 ByteBuf 也是基于这个基础的。
NormalSubPageHeapCaches 3种规格
进行分配如果该节点不能分配,交给兄弟节点分配
HeapArena
ChunkList
SmallSubPageDirectCaches定位MRCache
分配成功
收藏
收藏
0 条评论
下一页