前言
正是Netty的易用性和高性能成就了Netty,让其能够如此流行。
而作为一款通信框架,首当其冲的便是对IO性能的高要求。
不少读者都知道Netty底层通过使用Direct Memory,减少了内核态与用户态之间的内存拷贝,加快了IO速率。但是频繁的向系统申请Direct Memory,并再使用完成后释放本身就是一件影响性能的事情。为此,Netty内部实现了一套自己的内存管理机制,在申请时,Netty会一次性向操作系统申请较大的一块内存,然后再将大内存进行管理,按需拆分成小块分配。而释放时,Netty并不着急直接释放内存,而是将内存回收以待下次使用。
这套内存管理机制不仅可以管理Directory Memory,同样可以管理Heap Memory。
内存的终端消费者——ByteBuf
这里,我想向读者们强调一点,ByteBuf和内存其实是两个概念,要区分理解。
ByteBuf是一个对象,需要给他分配一块内存,它才能正常工作。
而内存可以通俗的理解成我们操作系统的内存,虽然申请到的内存也是需要依赖载体存储的:堆内存时,通过byte[], 而Direct内存,则是Nio的ByteBuffer(因此Java使用Direct Memory的能力是JDK中Nio包提供的)。
为什么要强调这两个概念,是因为Netty的内存池(或者称内存管理机制)涉及的是针对内存的分配和回收,而Netty的ByteBuf的回收则是另一种叫做对象池的技术(通过Recycler实现)。
虽然这两者总是伴随着一起使用,但这二者是独立的两套机制。可能存在着某次创建ByteBuf时,ByteBuf是回收使用的,而内存却是新向操作系统申请的。也可能存在某次创建ByteBuf时,ByteBuf是新创建的,而内存却是回收使用的。
因为对于一次创建过程而言,可以分成三个步骤:
- 获取ByteBuf实例(可能新建,也可能是之间缓存的)
- 向Netty内存管理机制申请内存(可能新向操作系统申请,也可能是之前回收的)
- 将申请到的内存分配给ByteBuf使用
本文只关注内存的管理机制,因此不会过多的对对象回收机制做解释。
Netty中内存管理的相关类
Netty中与内存管理相关的类有很多。框架内部提供了PoolArena,PoolChunkList,PoolChunk,PoolSubpage等用来管理一块或一组内存。
而对外,提供了ByteBufAllocator供用户进行操作。
接下来,我们会先对这几个类做一定程度的介绍,在通过ByteBufAllocator了解内存分配和回收的流程。
为了篇幅和可读性考虑,本文不会涉及到大量很详细的代码说明,而主要是通过图辅之必要的代码进行介绍。
针对代码的注解,可以见我GitHub上的netty项目。
PoolChunck——Netty向OS申请的最小内存
上文已经介绍了,为了减少频繁的向操作系统申请内存的情况,Netty会一次性申请一块较大的内存。而后对这块内存进行管理,每次按需将其中的一部分分配给内存使用者(每ByteBuf)。这里的内存就是PoolChunk,其大小由ChunkSize决定(默认为16M,即一次向OS申请16M的内存)。
Page——PoolChunck所管理的最小内存单位
PoolChunk所能管理的最小内存叫做Page