netty内存管理--分配过程

背景

在网络请求操作中,涉及大量读写,背后是大量的内存申请和回收。 这部分工作通过GC来完成,会带来比较大的GC压力,频繁的申请和释放内存对GC不友好,因此Netty开发了一个内存池模块用于内存的分配和回收来降低GC压力。核心思路就是事先申请一大块内存进行分配和回收,来避免JVM的GC操作。透过测试,内存池可以有效的平稳GC抖动。
netty在成功获取unsafe实例之后,进行堆外内存分配,否则进行堆内存分配。
return PlatformDependent.hasUnsafe() ? this.directBuffer(initialCapacity) : this.heapBuffer(initialCapacity);

几个核心分配类

1.Chunk:大块的内存,默认大小16m,完整的一颗平衡二叉树,包含4096个节点,其中叶子节点2048个,每个page(叶子节点)大小为8k。

class PoolChunk<T> {
	private static final int INTEGER_SIZE_MINUS_ONE = 31;
	final PoolArena<T> arena;    
	final T memory;                                        // directByteBuffer
	final boolean unpooled;                                    // false
	private final int pageSize;                                  // 8192 8k
	private final int maxOrder;                                  // 深度11层
	private final int chunkSize;                                  // chunk大小16m
	private int freeBytes;                                     // 空闲字段
	PoolChunkList<T> parent;
	PoolChunk<T> prev;
	PoolChunk<T> next;
	。。。
}
  1. PoolArena:所有内存分配的入口
class PoolArena<T>  {
	private final int maxOrder;                                // 层数11
	final int pageSize;                                        // 叶子节点大小8k
	final int chunkSize;                                       // 堆内存大小16m
	private final PoolSubpage<T>[] tinySubpagePools;           // tiny内存(小于512byte的内存),长度32
	private final PoolSubpage<T>[] smallSubpagePools;          // small内存块(大于512,小于节点大小)
	private final PoolChunkList<T> q050;                       // 利用率50%的集合,首查!
	private final PoolChunkList<T> q025;                       // 利用率25%的集合
	private final PoolChunkList<T> q000;                       // 利用率0%的集合
	private final PoolChunkList<T> qInit;                      // 初始化的chunk
	private final PoolChunkList<T> q075;                       // 利用率75%的集合
	private final PoolChunkList<T> q100;                       // 利用率100%的集合
。。。
}
  1. PoolSubpage:将叶子节点据需拆分成tiny内存块和small内存块。

内存分配流程

1.尝试从ThreadCache中寻找可用内存,如果找到拿出直接使用,如果未找到,则从arena开始申请内存,后续所有操作加锁,所以netty在一段时间使用后会更快一些(放入ThreadCache后),如果刚开始并发较高,可尝试预热。
2.如果是大块内存,大于16m(大于整个chunk),直接走堆外内存分配,不进行缓存。
3.如果小于chunk(16m),大于叶子节点,则使用二叉树前序遍历进行查询,根据格式化的内存要求算出层数以后遍历到指定深度为止。
4.如果小于叶子节点,则初始化一个叶子节点为PoolSubpage。内含tiny数组,small数组(tiny,small后续再细分)。根据格式化的内存需求找对应内存块。
5.内存使用完毕后,如果初始化和使用是同一个线程,则防御ThreadCache,避开锁竞争。
6.ChunkList包含一系列分使用率的chunk,需要从chunk分配时,先走使用率50%,再找一下,最后再找使用率75%,没有则初始化一个chunk。

如果尝试修改测试性能,下列参数可加入java启动中.
在这里插入图片描述

内存分配

chunk由2048个叶子节点构成,每个父节点的容量是子节点容量相加,每个叶子的大小是一个page。
叶子节点的初始容量为8k,如果进行分配,则剩余容量为0.
父节点的容量是两个叶子节点的较大值,如果叶子节点都未分配,则是两者之和。
通过层级计算得到可分配的二叉树节点,就算出目标层级后,用前序遍历算出可分配的叶子节点。
分配成功后,本节点的容量设置为0。
chunk使用的是平衡二叉树,用一个2048的数组对这棵树进行管理维护,数组存储的是下标节点当前可分配容量,申请成功后,返回当前节点的下表值,当前下表的值再设置为0,分配完成。
如果针对小容量进行分配(小于page的),会将page进行拆分

释放内存

将释放节点的可分配容量从0恢复到1。
随着内存的使用,chunk的使用率也在进行变化,为了提高命中率,Arena使用list对chunk进行管理分配,chunklist代表着不同的使用空间。

缓存线程

内存的分配是从arena开始的,每次分配都是需要上锁,非常影响性能,netty这里也是用了localthread对申请的内存块进行缓存。
每次使用内存是先走poolthreadcache中查找合适的内存,找不到则走arena分配。
每次使用完内存也尝试往poolThreadCache中放入。

PoolThreadCache

包含不同大小的内存块,当需要内存时,先从不同大小的内存块中分配,如果获取成功分配完成。

超大容量

如果需要的内存超过了chunk,那么这次申请则不会走chunk的分配,是一个一次性分配,申请一个堆外相同大小的内存,但是这种分配速度较慢。

总结:

当执行分配操作时,会先尝试从缓存中获取,如果获取成功,则不必走上锁的arena分配,省时省心无竞争,对性能提升有一定的帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值