Netty内存池架构
2021-04-29 20:04:36   38  举报             
     
         
 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 条评论
 下一页
  
  
  
  
  
  
  
  
  
 