前言
从这篇开始的后面几篇,我会讲下关于池化的字节缓冲区,这个要比非池化的复杂很多,里面涉及到netty自己的一套内存管理方案,其中还涉及到一些算法和位运算,并不是很简单的就能理解,不过我会慢慢讲,尽量讲的细点,因为这个是很核心的东西,这套内存分配算法在JVM
里也有类似的,就是我这篇文章讲的jemalloc
。当然我还会用很多的图和例子来解释下,因为用比较复杂,用文字很难说明,图是效率最高的,让我们开始吧。
PooledByteBufAllocator
为什么我要先讲这个呢,因为netty的内存管理方案类似jemalloc
,因此相对来说比较复杂,为什么负责呢,因为他把内存进行了分割,那如何来分割,分割成什么样的大小,很多依据都是来自这个分配器里的,所以得先把这个了解下,不然后面你可能会问,为什么chunk
是16m
,为什么pageSize
是8k
,因为都定义在他里面。首先我要说的下大致的内存分配结构。
Arena区域
翻译过来是竞技场,我比较喜欢叫他区域,他是对应多个线程的,默认开始是一个线程对应一个区域,这样多线程竞争就会少很多,除非一个CPU
上的两个线程很不巧数据都在同一个缓存行里面就可能会有竞争。
Chunk块
这个默认是一般的16M
大小的内存区域,比如堆内内存的话内部其实就是16M
的字节数组,其实他也由很多页组成的。
Page页
默认8K
大小,是内存分区最基本的单位,其实Chunk
块就是页的组合,后面的Tiny/Small
是对页内部再继续分区。
内存规格分类
Tiny
大小不定,从16B
开始,按16B
递增,一直到496B
,比如16,32,48,64,...,480,496
。总共31
种。
Small
大小不定,从512B
开始,按两倍递增,一直到4KB
,比如512,1k,2k,4k
。总共4
种。
Normal
也可以叫做Run
,大小不定,从8k
开始到16m
。
Huge
大于16m
的。
图示
默认的尺寸图示:
组成关系:
整体的大致图:
主要变量
先看一些主要的静态变量,这些都会在静态代码块中初始化。
private static final int DEFAULT_NUM_HEAP_ARENA;//堆缓冲区区域的数量 默认16
private static final int DEFAULT_NUM_DIRECT_ARENA;//直接缓冲区区域的数量 默认16
private static final int DEFAULT_PAGE_SIZE;//页大小 默认8192
private static final int DEFAULT_MAX_ORDER;//满二叉树的最大深度 默认11
private static final int DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT;//直接内存对齐 默认0
static final int DEFAULT_MAX_CACHED_BYTEBUFFERS_PER_CHUNK;//每个块中最大字节缓冲区的数量 和ArrayDeque有关 默认1023
private static final int DEFAULT_TINY_CACHE_SIZE;//TINY缓存数量 默认512
private static final int DEFAULT_SMALL_CACHE_SIZE;//SMALL缓存数量 默认256
private static final int DEFAULT_NORMAL_CACHE_SIZE;//NORMAL缓存数量 默认64
主要变量初始化
DEFAULT_PAGE_SIZE(io.netty.allocator.pageSize)
内存管理是把内存分成一个个页的,然后再对页进行分割和组合,默认页大小8k
。
DEFAULT_MAX_ORDER(io.netty.allocator.maxOrder)
这个是为了将chunk
进行页大小的分割而使用的一棵满二叉树的最大深度,默认是11
,也就是4095
个结点,最深的一层是2048
个节点,每个节点对应一个页大小,也即最深一层的容量就是一个chunk
大小,8k x 2048=16m
。
DEFAULT_NUM_HEAP_ARENA和DEFAULT_NUM_DIRECT_ARENA
首先是先计算出了chunk
的大小:final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
。8k<<11=16M
。
然后计算堆缓冲区和直接缓冲区分配的区域ARENA
的数量,一般是CPU
个数的2
倍。
这里有段runtime.maxMemory() / defaultChunkSize / 2 / 3
的意思就是说,获取可用的最大内存,然后除以chunk
的个数,除以2
(因为每个区域个数不能超过50%
呀),而且得有3
个chunk
,所以又除以3
。当然这个值算出来貌似可以大于16m
,但是太大了内部碎片多,分配灵活性也不好。当然这里还有两个参数可以调io.netty.allocator.numHeapArenas,io.netty.allocator.numDirectArenas
。
还有一些也是在静态代码段初始化的,比较好理解,就不多说了。
如何使用
我们来看看他是怎么用的,比如这一句代码:
他调用了ByteBufUtil
的默认分配器。
ByteBufUtil静态代码段
静态代码执行的时候会发现默认是池化的,然后调用的是PooledByteBufAllocator.DEFAULT
:
PooledByteBufAllocator.DEFAULT
默认是偏好直接内存的:
PooledByteBufAllocator最终构造函数
最后就调用到这里,就是做了一些参数的设置,还有就是Arena
区域的创建和一些指标的创建,具体细节后面会详细讲。
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
int tinyCacheSize, int smallCacheSize, int normalCacheSize,
boolean useCacheForAllThreads, int directMemoryCacheAlignment) {
super(preferDirect);
threadCache = new PoolThreadLocalCache(useCacheForAllThreads);//使用线程缓存
this.tinyCacheSize = tinyCacheSize;//TINY缓存数量 默认512
this.smallCacheSize = smallCacheSize;//SMALL缓存数量 默认256
this.normalCacheSize = normalCacheSize;//NORMAL缓存数量 默认64
chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);//计算块大小。默认16m
checkPositiveOrZero(nHeapArena, "nHeapArena");
checkPositiveOrZero(nDirectArena, "nDirectArena");
checkPositiveOrZero(directMemoryCacheAlignment, "directMemoryCacheAlignment");
if (directMemoryCacheAlignment > 0 && !isDirectMemoryCacheAlignmentSupported()) {
throw new IllegalArgumentException("directMemoryCacheAlignment is not supported");
}
//检查只能是2的倍数
if ((directMemoryCacheAlignment & -directMemoryCacheAlignment) != directMemoryCacheAlignment) {
throw new IllegalArgumentException("directMemoryCacheAlignment: "
+ directMemoryCacheAlignment + " (expected: power of two)");
}
//page大小是1<<pageShift得到 默认13
int pageShifts = validateAndCalculatePageShifts(pageSize);
//堆区域初始化
if (nHeapArena > 0) {
heapArenas = newArenaArray(nHeapArena);
List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
for (int i = 0; i < heapArenas.length; i ++) {
PoolArena.HeapArena arena = new PoolArena.HeapArena(this,
pageSize, maxOrder, pageShifts, chunkSize,
directMemoryCacheAlignment);
heapArenas[i] = arena;
metrics.add(arena);
}
heapArenaMetrics = Collections.unmodifiableList(metrics);
} else {
heapArenas = null;
heapArenaMetrics = Collections.emptyList();
}
//直接缓冲区区域初始化
if (nDirectArena > 0) {
directArenas = newArenaArray(nDirectArena);
List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(directArenas.length);
for (int i = 0; i < directArenas.length; i ++) {
PoolArena.DirectArena arena = new PoolArena.DirectArena(
this, pageSize, maxOrder, pageShifts, chunkSize, directMemoryCacheAlignment);
directArenas[i] = arena;
metrics.add(arena);
}
directArenaMetrics = Collections.unmodifiableList(metrics);
} else {
directArenas = null;
directArenaMetrics = Collections.emptyList();
}
metric = new PooledByteBufAllocatorMetric(this);
}
validateAndCalculatePageShifts
计算pageSize
是2
的多少次。默认大小8k
,是2
的13
次。
//验证并计算页的移动
private static int validateAndCalculatePageShifts(int pageSize) {
if (pageSize < MIN_PAGE_SIZE) {
throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + ")");
}
//判断是不是2的幂次方 不为0就不是
if ((pageSize & pageSize - 1) != 0) {
throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: power of 2)");
}
// Logarithm base 2. At this point we know that pageSize is a power of two. Integer.numberOfLeadingZeros最高位之前有多少个0
return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize);
}
PooledByteBufAllocator的heapBuffer
区域创建完了,开始申请堆缓冲区:
做了些非0
验证的事:
最后是到这里,先获取线程本地缓存,如果heapArena
不为空的话没有就调用allocate
,否则就用非池化的堆缓冲区。
@Override
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
PoolThreadCache cache = threadCache.get();
PoolArena<byte[]> heapArena = cache.heapArena;
final ByteBuf buf;
if (heapArena != null) {
buf = heapArena.allocate(cache, initialCapacity, maxCapacity);
} else {
buf = PlatformDependent.hasUnsafe() ?
new UnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
return toLeakAwareBuffer(buf);//转成泄露检测的
}
后面就是讲PoolArena
开始分配堆内存了,后面要花比较多的篇幅详细讲解细节,今天有个大致的了解,内存怎么分区的,大致有哪些尺寸,有哪些参数可以设置。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。