无题
无题
John DoeNetty剖析
1. Rector模型
2. 核心组件
- EventLoop:事件循环,处理注册到内核中关注的事件集合
- EventLoopGroup:管理EventLoop,读写事件操作时由next()方法进行EventLoop的负载均衡,如接收到一个连接,由EventLoopGroup负载到EventLoop并注册内核事件,后续有关该事件的操作都由该EventLoop操作
- Channel:该Channel可以类比于底层的套接字,可以基于该channel进行操作
- UnSafe:Channel进行的读写等操作通过Pipeline后,最终进行实际操作的都为该类,并且每一个Channel都有属于自己的Unsafe实现,该类可与底层套接字操作,如注册感兴趣的事件等
- Pipeline:每一个Channel都绑定了一个Pipeline,每一个Pipeline里面又包含多个 ChannelHandlerContext
- ChannelHandlerContext:该组件封装了ChannelHandler,将各个ChannelHandler封装起来,方便Pipeline构造双向链表
- ChannelHandler:实际处理数据的处理程序,包含 ChannelInboundHandler 和 ChannelOutboundHandler
- ChannelInboundInvoker 和 ChannelOutboundInvoker:负责触发 ChannelInboundHandler 和 ChannelOutboundHandler的执行
3. EventLoop
可以看做只有一个线程的线程池,由于EventLoopGroup是EventLoop的合集,所以最终任务的执行都会交由EventLoop执行
EventLoop事件循环的启动流程
4. Server端服务Channel接收流程
5. Pipeline
6. ByteBuf
可以看到ByteBuf继承自ReferenceCounted、Comparable、ByteBufConvertible
ReferenceCounted:引用计数对象的接口,该接口规定了引用计数的基本操作,诸如引用计数增加、引用计数释放,Netty该设计为了防止ByteBuffer的内存泄漏
ByteBufConvertible:返回ByteBuf自身的接口
ByteBuf会在写入操作时自动扩容
7. Netty内存管理
- PoolChunk:Netty向操作系统申请内存的最小单位(默认值4M),是Run的集合
- Run: 对应一块连续的内存,大小是Page的倍数
- Page: Chunk的最小分配单元,默认大小为8K,一个Chunk默认有512个Page
- Subpage: 负责Page内的内存分配,目的是为了减少内存的浪费。如果需要分配的内存小于Page的大小(8K)比如只有100B,如果直接分配一个Page(8K)那就直接浪费了。SubPage可以理解为由一个或多个page组成的,并且可以分配申请内存小于28K的内存
如下为PoolTrunkList 的结构:
如下为PoolArena中的一些结构
Arena只是作为内存分配的工具,底层内存最终的分配通过PoolTrunk实现,而在进行PoolTrunk内存分配的时候,有三个层级的内存分配
概念解读:
可以这样理解PoolArena内存分配的API入口,PoolArena中包含很多的PoolTrunk,而PoolTrunk中又包含很多的run,run是PoolTrunk分配的最小单位
在上面理解中存在PoolSubpeage,可以把PollSubpage也理解为一个run,特别之处在于PoolSubpage是具有分配小内存的run,并且PookSubpage也提供了内存分配接口
在PoolSubpage内部,可以通过标识的bitMap,该数组是long[]类型,每一位表示该PoolSubpage的哪些部分内存已被分配
内存释放过程:
run
run是PoolTrunk分配的最小单位,在进行PooledByteBufAllocator池化内存分配的时候,即创建一个ByteBuf对象,每个PooledByteBuf对象内部都维护着一个long型handle字段,该字段标识着该ByteBuf在在PoolTrunk中的位置,该handle字段释义:
如下面示例
1 | 代码片段: |
可以看出,对于byteBuf1,由于是首次内存分配,会先创建PoolTrunk,分配的空间为16B,所以小于28K,PoolTrunk分配时创建Subpage,因此run的页偏移为0,该run占用的page数量为1,即8K,标记该
byteBuf1:000000000000000 表示ByteBuf所在的run相对PoolTrunk的偏移为0,
000000000000001 表示该run共占用1个page,即8K
1 表示该run已经被分配
1 表示该run为一个PoolSubpage
00000000000000000000000000000000 表示位于该PoolSubpage的相对元素的偏移为0
byteBuf2:000000000000000 表示ByteBuf所在的run相对PoolTrunk的偏移为0,
000000000000001 表示该run共占用1个page,即8K
1 表示该run已经被分配
1 表示该run为一个PoolSubpage
00000000000000000000000000000001 表示位于该PoolSubpage的相对0位置元素的偏移为1
byteBuf3:000000000000000 表示ByteBuf所在的run相对PoolTrunk的偏移为0,
000000000000001 表示该run共占用1个page,即8K
1 表示该run已经被分配
1 表示该run为一个PoolSubpage
00000000000000000000000000000010 表示位于该PoolSubpage的相对0位置元素的偏移为2
byteBuf4:000000000000001 表示ByteBuf所在的run相对PoolTrunk的偏移为1,即该run是从PoolTrunk的第1个page开始
000000000000100 表示该run共占用1个page,即8K
1 表示该run已经被分配
0 表示该run为一个PoolSubpage
00000000000000000000000000000000 不是PoolSubpage时无意思
byteBuf4:000000000000101 表示ByteBuf所在的run相对PoolTrunk的偏移为5,即该run是从PoolTrunk的第5个page开始
000000000000100 表示该run共占用1个page,即8K
1 表示该run已经被分配
0 表示该run为一个PoolSubpage
00000000000000000000000000000000 不是PoolSubpage时无意思
通过分析发现,对于subpage来说,由于分配时相同尺寸内存都由规定的尺寸分配,所以分配16KB由于偏移为0的run还未存满,所以都是对应同一个值,当分配512次16B之后,该run已经存满,这是再分配16B才会开辟新的run,这是run偏移个规格才会变化
而对于需要分配的内存大于28K来说,如byteBuffer4和byteBuffer5,他们在分配的时候就会以run为单位分配,这个相对更容易理解














