2023-09-13
原文作者:https://blog.csdn.net/wangwei19871103/category_9681495_2.html 原文地址: https://blog.csdn.net/wangwei19871103/article/details/104328943

前言

从这篇开始的后面几篇,我会讲下关于池化的字节缓冲区,这个要比非池化的复杂很多,里面涉及到netty自己的一套内存管理方案,其中还涉及到一些算法和位运算,并不是很简单的就能理解,不过我会慢慢讲,尽量讲的细点,因为这个是很核心的东西,这套内存分配算法在JVM里也有类似的,就是我这篇文章讲的jemalloc。当然我还会用很多的图和例子来解释下,因为用比较复杂,用文字很难说明,图是效率最高的,让我们开始吧。

PooledByteBufAllocator

为什么我要先讲这个呢,因为netty的内存管理方案类似jemalloc,因此相对来说比较复杂,为什么负责呢,因为他把内存进行了分割,那如何来分割,分割成什么样的大小,很多依据都是来自这个分配器里的,所以得先把这个了解下,不然后面你可能会问,为什么chunk16m,为什么pageSize8k,因为都定义在他里面。首先我要说的下大致的内存分配结构。

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的。

图示

默认的尺寸图示:

202309132201153741.png
组成关系:

202309132201160852.png
整体的大致图:

202309132201179533.png

主要变量

先看一些主要的静态变量,这些都会在静态代码块中初始化。

     	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

202309132201188734.png

DEFAULT_MAX_ORDER(io.netty.allocator.maxOrder)

这个是为了将chunk进行页大小的分割而使用的一棵满二叉树的最大深度,默认是11,也就是4095个结点,最深的一层是2048个节点,每个节点对应一个页大小,也即最深一层的容量就是一个chunk大小,8k x 2048=16m

202309132201200345.png

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%呀),而且得有3chunk,所以又除以3。当然这个值算出来貌似可以大于16m,但是太大了内部碎片多,分配灵活性也不好。当然这里还有两个参数可以调io.netty.allocator.numHeapArenas,io.netty.allocator.numDirectArenas

202309132201210966.png
还有一些也是在静态代码段初始化的,比较好理解,就不多说了。

如何使用

我们来看看他是怎么用的,比如这一句代码:

202309132201221897.png
他调用了ByteBufUtil的默认分配器。

202309132201228118.png

ByteBufUtil静态代码段

静态代码执行的时候会发现默认是池化的,然后调用的是PooledByteBufAllocator.DEFAULT

202309132201235659.png

PooledByteBufAllocator.DEFAULT

2023091322012461910.png
默认是偏好直接内存的:

2023091322012535711.png

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

计算pageSize2的多少次。默认大小8k,是213次。

    //验证并计算页的移动
        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

区域创建完了,开始申请堆缓冲区:

2023091322012638112.png
做了些非0验证的事:

2023091322012726113.png
最后是到这里,先获取线程本地缓存,如果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开始分配堆内存了,后面要花比较多的篇幅详细讲解细节,今天有个大致的了解,内存怎么分区的,大致有哪些尺寸,有哪些参数可以设置。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

阅读全文