特点是内存的分配和回收速度快,可以被 JVM自动回收;缺点就是如果进行Socket的I/O读写,需要额外做一次内存复制,将堆 内存对应的缓冲区复制到内核Channel中,性能会有一定程度的下降。
非堆内存,它在堆外进行内存分配,相 比于堆内存,它的分配和回收速度会慢一些,但是将它写入或者从Socket Channel中读取 时,由于少了一次内存复制,速度比堆内存快。
正是因为各有利弊,所以Netty提供了多种ByteBuf供开发者使用,经验表明,ByteBuf 的最佳实践是在I/O通信线程的读写缓冲区使用DirectByteBuf,后端业务消息的编解码模 块使用HeapByteBuf,这样组合可以达到性能最优。
从内存回收角度看,ByteBuf也分为两类:基于对象池的ByteBuf和普通ByteBufo两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护了一个内存池,可以循环利用创建的ByteBuf,提升内存的使用效率,降低由于高负我导致的频繁GC。测试表明使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。 尽管推荐使用基于内存池的ByteBuf ,但是内存池的管理和维护更加复杂,使用起来也需要更加谨慎,因此,Netty提供了灵活的策略供使用者来做选择。下面我们对主要的功能类和方法的源码进行分析和解读,以便能够更加深刻地理解ByteBuf的实现,掌握其更加高级的功能。
从内存回收角度看,ByteBuf也分为两类:基于对象池的ByteBuf和普通ByteBuf两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护了一个内存池,可以循环利用创建的ByteBuf,提升内存的使用效率,降低由于高负载导致的频繁GC。
测试表明使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。尽管推荐使用基于内存池的ByteBuf ,但是内存池的管理和维护更加复杂,使用起来也需要更加谨慎,因此,Netty提供了灵活的策略供使用者来做选择。下面我们对主要的功能类和方法的源码进行分析和解读,以便能够更加深刻地理解 ByteBuf的实现,掌握其更加高级的功能。
如果扩容后的新容量小于阈值 ,则以64为计数进行倍增,直到倍增后的结果大于或等于需要的容量值。采用倍增或者步进算法的原因如下:如果以minNewCapacity作为目标容量,则本次扩容后的可写字节数刚好够本次写入使用。写入完成后 ,它的可写字节数会变为 0 , 下次做写入操作的时候,需要再次动态扩张。这样就会形成第一次动态扩张后,每次写入操作都会进行动态扩张,由于动态扩张需要进行内存熨制,频繁的内存豆制会导致性能下降。
采用先倍增后步进的原因如下:当内存比较小的情况下,倍增操作并不会带来太多的内存浪纸,例如64字节一>128字节->256字节,这样的内存扩张方式对于大多数应用系 统是可以接受的。但是,当内存增长到一定阈值后,再进行倍增就可能会带来额外的内存浪费,例如10MB,采用倍增后变为20MB。但很有可能系统只需要12MB,则扩张到20MB后 会 带 来 8MB的内存浪费 。 由于每个客户端连接都可能维护自己独立的接收和发送缓 冲 区,这样随着客户读的线性增长,内存浪费也会成比例地增加,因此,达到某个阈值后就需要以步进的方式对内存进行平滑的扩张。
从内存回收角度看,ByteBuf也分为两类:基于对象池的ByteBuf和普通ByteBufo两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护了一个内存 池,可以循环利用创建的ByteBuf,提升内存的使用效率,降低由于高负载导致的频繁GC。 测试表明使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。 尽管推荐使用基于内存池的ByteBuf,但是内存池的管理和维护更加复杂,使用起来 也需要更加谨慎,因此,Netty提供了灵活的策略供使用者来做选择。
由于ByteBuf内存池的实现涉及到的类和数据结构非常多,限于篇幅,本章节不对其源码进行展开说明,而是从设计原理角度来讲解内存池的实现。
Arena本身是指一块区域,在内存管理中,Memory Arena是指内存中的一大块连续的内存区域,PoolArena就是Netty的内存池实现类。为了集中管理内存的分配和释放,同时提高分配和释放内存时候的性能,很多框架和应用都会通过预先申请一大块内存,然后通过提供相应的分配和释放接口来使用内存。这样一来,对内存的管理就被集中到几个类或者函数中,由于不再频繁使用系统调用来申请和释放内存,应用或者系统的性能也会大大提高。在这种设计思路下,预先申请的那一大块内存就被称为Memory Arena不同的框架,Memory Arena的实现不同,Netty的PoolArena是由多个Chunk组成的大块内存区域,而每个Chunk则由一个或者多个Page组成,因此,对内存的组织和管理也就主要集中在如何管理和组织Chunk和Page 了。
Chunk主要用来组织和管理多个Page的内存分配和器放,在Netty中,Chunk中的Page被构建成,平衡二叉树。假设一个Chunk由16个Page组成,那么这些Page将会被按照下图数据结构所示的形式组织起来。Page的大小是4个字节,Chunk的大小是64个字节( 4 x 16).整棵树有5层,第1层(也就是叶子节点所在的层)用来分配所有Page的内存,第4层用来分配2个Page的内存,依此类推。每个节点都记录了自己在性个Memory Arena中的偏移地址,当一个节点代表的内存区域被分配出去之后,这个节点就会被标记为已分配,自这个节点以下的所有节点在后面的内存分配请求中都会被忽略。举例来说,当我们请求一个16字节的存储区域时,上面这个树中的第3层中的4个节点中的一个就会被标记为已分配,这就表示整个Memroy Arena中有16个字节被分配出去了,新的分配请求只能从剩下的3个节点及其子树中寻找合适的节点。