吃透Netty源码系列十九之池化内存分配预备知识

前言

从这篇开始的后面几篇,我会讲下关于池化的字节缓冲区,这个要比非池化的复杂很多,里面涉及到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的。

图示

默认的尺寸图示:
在这里插入图片描述
组成关系:
在这里插入图片描述
整体的大致图:
在这里插入图片描述

主要变量

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

 	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%呀),而且得有3chunk,所以又除以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

计算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

区域创建完了,开始申请堆缓冲区:
在这里插入图片描述
做了些非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开始分配堆内存了,后面要花比较多的篇幅详细讲解细节,今天有个大致的了解,内存怎么分区的,大致有哪些尺寸,有哪些参数可以设置。

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值