系列文章目录
1. Netty网络应用基础
2. Java I/O
3. IO/模型
4. 网络应用编解码
5. Netty Pipeline
6. Netty EventLoopGroup&EventLoop
7. Netty ThreadLocal&FastThreadLocal
8. Netty Future&Promise
9. Netty内存管理–(旧)PoolChunk&伙伴分配
10. Netty内存管理–内存池空间规格化SizeClasses
11. Netty内存管理–PoolChunk&PoolSubPage
12. Netty内存管理–内存池PoolArena
13. Netty内存管理–内存分配器PooledByteBufAllocator
14. Netty ObjectPool
一、写在前面
到这里想必你已了解了Netty内存规格化, PoolChunk、PoolSubPage、PoolArena3个关键的类。本篇想聊聊直接使用的内存分配器PooledByteBufAllocator。虽然背后还是由前面3个关键类做支持, 但是PooledByteBufAllocator基于实际场景做了一些额外的优化。本文重点关注在优化和支持优化的相关调整。
二、实际场景
问题 | 对策 |
---|---|
虽然PoolArena可以管理PoolChunk和PoolSubPage, 但并发场景下, 共享对象如何解决线程间竞争问题? | 创建线程数量相等的PoolArena, 每个线程绑定自己的PoolArena尽可能消除竞争 |
业务场景中的小对象(比如IM系统中的一条文字消息大多小于8K)其数量非常多, 频繁分配和回收浪费CPU? | 回收阶段延迟回收, 尽可能重用已分配内存而不是申请新内存 |
1. 尽可能消除竞争
- 鉴于Netty本身是网络通信库, 其内存管理面向网络IO场景。内存分配器最终被EventLoop结合, 实现高效的IO。默认的EventLoop数量是CPU核心数*2, 因此默认的分配器会创建对等数量的PoolArena。
- 前面已经聊过Netty中支持ThreadLocal优化相关的FastThreadLocal和FastThreadLocalThread在线程绑定这里也会发挥作用。
- 至于哪个PoolArena应该和哪个Thread绑定, 这是个负载均衡问题。Netty在PoolArena中增加了线程数字段, PooledByteBufAllocator会从所有的PoolArena中选择一个绑定线程数最小的给当前线程使用。
- 由于实际向OS申请空间是由线程申请触发, 因此最终创建的对象更多了, 但不会立刻占用太多内存。
2. 延迟回收(分配复用)
- PoolThreadCache作为PoolThreadLocalCache的实际内容, 其中支持对已分配的Normal和Small分配做缓存, 并在超过最大数量时释放部分缓存内容。
- 由于存在cache, 因此在后续的分配和回收中都需要增加cache维护相关逻辑, 相关的PoolChunk,PoolSubPage和PoolArena的代码中都可以看到PoolThreadCache参数;
三、逻辑调整
分配逻辑调整
- 分配空间前先尝试获得线程绑定的PoolThreadCache;
- 获得PoolThreadCache关联的PoolArena, 委托PoolArena完成实际的分配(PoolArean更下层的委托此处不再过多讨论);
- 将最终得到的PooledByteBuf关联到PoolThreadCache;
回收逻辑调整
- 回收空间时, 第一步缓存到关联的PoolThreadCache中, 具体对象为MemoryRegionCache;
- 如果由于超过最大容量缓存失败则释放掉对象本身, 空间则委托给PoolArena来释放(PoolArean更下层的委托此处不再过多讨论);
四、小结
以上就是PooledByteBufAllocator解决并发环境下的共享和性能问题过程, 基本思路是消除竞争和空间复用, 可以作为工程中类似问题的一个参考。