netty源码浅析-池化内存直接申请

Netty中的bytebuf使用直接内存和池化技术的意义在哪,以及具体的使用场景 阅读详情
池化内存直接申请

池化内存申请是我们的重点我们以下面方法为入口分析,池化内存的分配过程

PooledByteBufAllocator pooledByteBufAllocator = PooledByteBufAllocator.DEFAULT;
pooledByteBufAllocator.buffer(100);
 //根据平台是否支持直接内存创建PooledByteBufAllocator
 public static final PooledByteBufAllocator DEFAULT =
            new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());

我们逐步跟踪进入PooledByteBufAllocator的构造函数中

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);//PoolThreadLocalCache-> FastThreadLocal->ThreadLocal
        this.tinyCacheSize = tinyCacheSize;//512
        this.smallCacheSize = smallCacheSize;//256
        this.normalCacheSize = normalCacheSize;//64
        chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);//预申请的内存块大小为pageSize*2^maxOrder=16M

        checkPositiveOrZero(nHeapArena, "nHeapArena");//nHeapArena必须为正数 core*2或者是可以分配可以分配内存块的三分之二
        checkPositiveOrZero(nDirectArena, "nDirectArena");//nDirectArena必须为正数core*2或者是可以分配可以分配内存块的三分之二

        checkPositiveOrZero(directMemoryCacheAlignment, "directMemoryCacheAlignment");//directMemoryCacheAlignment必须为正数 0
        if (directMemoryCacheAlignment > 0 && !isDirectMemoryCacheAlignmentSupported()) {
            throw new IllegalArgumentException("directMemoryCacheAlignment is not supported");
        }
        //directMemoryCacheAlignment必须是二的次方
        if ((directMemoryCacheAlignment & -directMemoryCacheAlignment) != directMemoryCacheAlignment) {
            throw new IllegalArgumentException("directMemoryCacheAlignment: "
                    + directMemoryCacheAlignment + " (expected: power of two)");
        }

        int pageShifts = validateAndCalculatePageShifts(pageSize);//2^13=8192--》13

        if (nHeapArena > 0) {
            heapArenas = newArenaArray(nHeapArena);//new PoolArena[cpu*2]
            List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
            //循环初始化,每个线程创建一个heapArenas
            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);
    }

我们将上面的构造函数方法分开来看首先是对一些值进行赋值然后对一些值进行校验


        if (nHeapArena > 0) {
            heapArenas = newArenaArray(nHeapArena);//new PoolArena[nHeapArena]
            List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
            //循环初始化,每个线程创建一个heapArenas
            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();
        }

这里nHeapArena就是core*2或者是可以分配内存块数量的三分之二,这里如果核心线程数的二倍那就和evenLoopGroup中线程的数量相同,这里创建了 new HeapArena[size],然后进行初始化

HeapArena(PooledByteBufAllocator parent, int pageSize, int maxOrder,
                int pageShifts, int chunkSize, int directMemoryCacheAlignment) {
            super(parent, pageSize, maxOrder, pageShifts, chunkSize,
                    directMemoryCacheAlignment);
        }

this.parent = parent;//PooledByteBufAllocator
        this.pageSize = pageSize;//8K
        this.maxOrder = maxOrder;//11
        this.pageShifts = pageShifts;//13
        this.chunkSize = chunkSize;//16M
        directMemoryCacheAlignment = cacheAlignment;//0
        directMemoryCacheAlignmentMask = cacheAlignment - 1;//-1
        subpageOverflowMask = ~(pageSize - 1);//-8192,其实就是相当于低13位全是0
        tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);//new PoolSubpage[32];
        //初始化32个subPage
        for (int i = 0; i < tinySubpagePools.length; i ++) {
            tinySubpagePools[i] = newSubpagePoolHead(pageSize);
        }

        numSmallSubpagePools = pageShifts - 9;//4
        smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);//new PoolSubpage[4];
        //初始化4个subPage
        for (int i = 0; i < smallSubpagePools.length; i ++) {
            smallSubpagePools[i] = newSubpagePoolHead(pageSize);
        }
        //创建PoolChunkList并组成双向链表,不过这里面qInit的pre指向自己,q00的pre指向null
        q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, chunkSize);
        q075 = new PoolChunkList<T>(this, q100, 75, 100, chunkSize);
        q050 = new PoolChunkList<T>(this, q075, 50, 100, chunkSize);
        q025 = new PoolChunkList<T>(this, q050, 25, 75, chunkSize);
        q000 = new PoolChunkList<T>(this, q025, 1, 50, chunkSize);
        qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, chunkSize);

        q100.prevList(q075);
        q075.prevList(q050);
        q050.prevList(q025);
        q025.prevList(q000);
        q000.prevList(null);
        qInit.prevList(qInit);

        List<PoolChunkListMetric> metrics = new ArrayList<PoolChunkListMetric>(6);
        metrics.add(qInit);
        metrics.add(q000);
        metrics.add(q025);
        metrics.add(q050);
        metrics.add(q075);
        metrics.add(q100);
        chunkListMetrics = Collections.unmodifiableList(metrics);
    }

上面的代码我们分开来看,首先执行一些赋值操作,然后创建了32个tiny类型的PoolSubpage和4个small类型的PoolSubpage,我们继续跟踪到PoolSubpage的内部

//初始化32个subPage
        for (int i = 0; i < tinySubpagePools.length; i ++) {
            tinySubpagePools[i] = newSubpagePoolHead(pageSize);
        }

        numSmallSubpagePools = pageShifts - 9;//4
        smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);//new PoolSubpage[4];
        //初始化4个subPage
        for (int i = 0; i < smallSubpagePools.length; i ++) {
            smallSubpagePools[i] = newSubpagePoolHead(pageSize);
        }

static final int numTinySubpagePools = 512 >>> 4;//32

private PoolSubpage<T> newSubpagePoolHead(int pageSize) {
        PoolSubpage<T> head = new PoolSubpage<T>(pageSize);
        head.prev = head;
        head.next = head;
        return head;
    }

PoolSubpage(int pageSize) {
        chunk = null;
        memoryMapIdx = -1;
        runOffset = -1;
        elemSize = -1;
        this.pageSize = pageSize;//8K
        bitmap = null;
    }

这里可以看到创建了一个长度位32的PoolSubpage,并进行了初始化,并且PoolSubpage是一个双向链表结构,首次创建时,头部节点指向自己。

numSmallSubpagePools = pageShifts - 9;//4
smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);//new PoolSubpage[4];
//初始化4个subPage
for (int i = 0; i < smallSubpagePools.length; i ++) {
    smallSubpagePools[i] = newSubpagePoolHead(pageSize);
}

可以看到然后又创建了长度为4的PoolSubpage数组,并且进行了初始化,然后形成了链表结构

//创建PoolChunkList并组成双向链表,不过这里面qInit的pre指向自己,q00的pre指向null
        q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, chunkSize);
        q075 = new PoolChunkList<T>(this, q100, 75, 100, chunkSize);
        q050 = new PoolChunkList<T>(this, q075, 50, 100, chunkSize);
        q025 = new PoolChunkList<T>(this, q050, 25, 75, chunkSize);
        q000 = new PoolChunkList<T>(this, q025, 1, 50, chunkSize);
        qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25, chunkSize);

        q100.prevList(q075);
        q075.prevList(q050);
        q050.prevList(q025);
        q025.prevList(q000);
        q000.prevList(null);
        qInit.prevList(qInit);

然后创建了6个PoolChunkList,他们代表每个chunk的使用率是多少

对象名最低使用率最高使用率
qInitInteger.MIN_VALUE25
q000150
q0252575
q05050100
q07575100
q100100Integer.MAX_VALUE

PoolChunkList是为了组织poolChunk,根据poolChunk的使用率移动到不同的PoolChunkList中,这样我们在下次申请内存的时候就可以直接通过PoolChunkList中的poolChunk进行申请。关于PoolChunk我们后面在介绍

然后将这些PoolChunkList除了qInit外,其他的组成一个双向链表,但是qInit的next执行q000。下面我们回到内存分配方法。这里我们可以看到使用率之间会有部分交集,主要可能是为了,防止使用率不断升高然后下降,导致poolChunk在双向链表中不停的移动。

public ByteBuf buffer(int initialCapacity) {
        if (directByDefault) {
            return directBuffer(initialCapacity);
        }
        return heapBuffer(initialCapacity);
    }

上面的代码我们在分析byteBuf分配器的时候也有提到最后会走到

protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity);

抽象方法,具体的实现交给子类完成,我们继续跟踪,来到PooledByteBufAllocator#newDirectBuffer,

protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
        //每个线程都有一个threadCache,第一次执行会在这里初始化
        PoolThreadCache cache = threadCache.get();
        PoolArena<ByteBuffer> directArena = cache.directArena;

        final ByteBuf buf;
        if (directArena != null) {
            buf = directArena.allocate(cache, initialCapacity, maxCapacity);
        } else {
            buf = PlatformDependent.hasUnsafe() ?
                    UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
                    new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
        }

        return toLeakAwareBuffer(buf);
    }

这里为了减少线程竞争,会给每个线程创建一个PoolThreadCache,可以从PoolThreadCache申请,这里我们先跳过,后面在分析通过缓存申请的情况,这里我们分析直接分配的情况。

protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
    //unsafe
            if (HAS_UNSAFE) {
                return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
            } else {
                return PooledDirectByteBuf.newInstance(maxCapacity);
            }
        }

static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
    //从对象回收站中获取一个对象,如果没有则直接创建
        PooledUnsafeDirectByteBuf buf = RECYCLER.get();
    //重置byteBuf,设置初始值
        buf.reuse(maxCapacity);
        return buf;
    }

final void reuse(int maxCapacity) {
        maxCapacity(maxCapacity);
        resetRefCnt();
        setIndex0(0, 0);
        discardMarks();
    }

这里我们看到会根据是safe或者是unsafe类型,从对象回收站中获取一个pooledByteBuf对象,如果获取不到就创建一个对象,然后设置一些初始值,比如将读写指针、标记指针赋值为0,设置最大容量。然后我们就可以使用和这个对象了。我们再次回到上面的申请方法

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
        //规范后值
        final int normCapacity = normalizeCapacity(reqCapacity);//272
        if (isTinyOrSmall(normCapacity)) { // 需要申请的大小是否小于8K
            int tableIdx;
            PoolSubpage<T>[] table;
            //是不是tiny类型
            boolean tiny = isTiny(normCapacity);
            if (tiny) { // < 512
                //先从缓存中分配内存,分配成功直接返回
                if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {//100 112
                    // was able to allocate out of the cache so move on
                    return;
                }
                tableIdx = tinyIdx(normCapacity);//7
                table = tinySubpagePools;
            } else {
                if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                    // was able to allocate out of the cache so move on
                    return;
                }
                tableIdx = smallIdx(normCapacity);
                table = smallSubpagePools;
            }
            //tiny类型table中分配normCapacity的位置
            final PoolSubpage<T> head = table[tableIdx];//table[7]

            /**
             * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and
             * {@link PoolChunk#free(long)} may modify the doubly linked list as well.
             */
            synchronized (head) {
                //第一次分配双向链表中就一个节点,head.next就是head
                final PoolSubpage<T> s = head.next;
                //如果分配内存大小之前已经分配过了
                if (s != head) {
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    long handle = s.allocate();
                    assert handle >= 0;
                    s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity);
                    incTinySmallAllocation(tiny);
                    return;
                }
            }
            //创建新的PoolChunk分配
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
            }

            incTinySmallAllocation(tiny);
            return;
        }
        //分配norm类型,8K-16M
        if (normCapacity <= chunkSize) {
            if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
                // was able to allocate out of the cache so move on
                return;
            }
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
                ++allocationsNormal;
            }
        } else {
            // Huge allocations are never served via the cache so just call allocateHuge
            //分配huge对象,超过了16M
            allocateHuge(buf, reqCapacity);
        }
    }

可以看到针对申请内存大小可以分为下面几种

内存类型范围
tiny0-512
small512-8K
normal8K-16M
huge16M以上
首先对申请值进行规范化处理
final int normCapacity = normalizeCapacity(reqCapacity);//112

int normalizeCapacity(int reqCapacity) {//默认为256
        checkPositiveOrZero(reqCapacity, "reqCapacity");

        if (reqCapacity >= chunkSize) {
            return directMemoryCacheAlignment == 0 ? reqCapacity : alignCapacity(reqCapacity);
        }
        //如果不是tiny类型则申请内存为大于申请值的2的幂值
        if (!isTiny(reqCapacity)) { // >= 512
            // Doubled

            int normalizedCapacity = reqCapacity;
            normalizedCapacity --;
            normalizedCapacity |= normalizedCapacity >>>  1;
            normalizedCapacity |= normalizedCapacity >>>  2;
            normalizedCapacity |= normalizedCapacity >>>  4;
            normalizedCapacity |= normalizedCapacity >>>  8;
            normalizedCapacity |= normalizedCapacity >>> 16;
            normalizedCapacity ++;

            if (normalizedCapacity < 0) {
                normalizedCapacity >>>= 1;
            }
            assert directMemoryCacheAlignment == 0 || (normalizedCapacity & directMemoryCacheAlignmentMask) == 0;

            return normalizedCapacity;
        }

        if (directMemoryCacheAlignment > 0) {
            return alignCapacity(reqCapacity);
        }

        // Quantum-spaced
        //申请大小为tiny类型,如果是16的倍数字节返回
        if ((reqCapacity & 15) == 0) {
            return reqCapacity;
        }
        //如果不是16的倍数,则是大于申请值的最小的16倍数值
        return (reqCapacity & ~15) + 16;
    }

这里如果申请的类型是tiny类型,如果申请的大小是16的倍数则直接返回,如果不是就调整值为大于申请值的最小的16倍数值,如果申请的不是tiny类型,而是其他几种类型,则申请的大小调整为大于申请值的最小2的幂次数值。这里我们申请的值是100,规范化后的数值为112。我们继续回到申请方法中,判断申请的内存大小是不是小于一个page的大小,如果是则会将page分割成申请大小的规范化后的值的subPage。

范围含义内存大小
chunk16M
page8K
subPage小于8K
netty申请内存是按照chunk向操作系统进行申请,page是chunk切分后的结果,一个chunk最多可以切分为2048个page,而subPage是对page分割的,subPage最小是16B大小,也就是一个page可以切分为512个subPage,这样当申请内存不足一个page时不会导致内存浪费。我们继续回到上面的方法。

判断申请的大小是不是不足一个page,如果是就需要对page进行切分,然后判断申请的类型是tiny类型还是small类型,这里我们申请的大小是tiny类型,然后会先通过cache进行申请,这个我们在下面分析,现在分析直接申请

tableIdx = tinyIdx(normCapacity);//7
//除16
static int tinyIdx(int normCapacity) {
        return normCapacity >>> 4;
    }

计算规范化的申请值在数组中的下标索引,然后获取tinySubpagePools对象位置的PoolSubpage,这个tinySubpagePools就是我们在上面说的在创建poolArena的时候会创建的长度为32的poolSubPage数组,我们上面分析过只是创建了这个poolSubpage,并且双向链表的head指向的是自己。然后进入同步方法

synchronized (head) {
                //第一次分配双向链表中就一个节点,head.next就是head
                final PoolSubpage<T> s = head.next;
                //如果分配内存大小之前已经分配过了
                if (s != head) {
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    long handle = s.allocate();
                    assert handle >= 0;
                    s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity);
                    incTinySmallAllocation(tiny);
                    return;
                }
            }

这里同步是为了防止同时有其他线程操作head,上面说了第一次创建的时候head.next指向的就是head,这里不会进入if方法,那if方法什么时候进入呢,其实本次申请内存后这个poolSubpage就会连接到head下,在下次申请相同大小的内存时,当然也不是完全相同是规范化的值相同时,就会进入if方法,找到上次分配的chunk直接分配。我们继续往下跟踪

//创建新的PoolChunk分配
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
            }

private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        //先在已有的poolChunkList中分配,第一次分配会失败
        if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
            q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
            q075.allocate(buf, reqCapacity, normCapacity)) {
            return;
        }

        // Add a new chunk.添加新的PoolChunk
        PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
        //开始分配内存
        boolean success = c.allocate(buf, reqCapacity, normCapacity);
        assert success;
        qInit.add(c);
    }

首先会在poolChunkList链表中查找chunk进行分配,如果申请成功就直接返回,这里我们是第一次申请还没有创建poolChunk,所以会执行下面的方法

// Add a new chunk.添加新的PoolChunk
        PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
        //开始分配内存
        boolean success = c.allocate(buf, reqCapacity, normCapacity);

这里会新建一个poolChunk进行内存分配,内存申请都是通过poolChunk来组织的我们来看看poolChunk的构造函数

PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize, int offset) {
        unpooled = false;
        this.arena = arena;
        this.memory = memory;//byte[16M]
        this.pageSize = pageSize;//8K
        this.pageShifts = pageShifts;//13
        this.maxOrder = maxOrder;//11
        this.chunkSize = chunkSize;//16M
        this.offset = offset;//0
        unusable = (byte) (maxOrder + 1);//12
        log2ChunkSize = log2(chunkSize);//24
        subpageOverflowMask = ~(pageSize - 1);//
        //剩余可分配内存大小
        freeBytes = chunkSize;

        assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
        maxSubpageAllocs = 1 << maxOrder;//2048

        // Generate the memory map.
        memoryMap = new byte[maxSubpageAllocs << 1];//new byte[4096]
        depthMap = new byte[memoryMap.length];//new byte[4096]
        int memoryMapIndex = 1;
        //构造一颗深度为11的完全二叉树,其实就是一个小顶堆
        for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
            int depth = 1 << d;
            for (int p = 0; p < depth; ++ p) {
                // in each level traverse left to right and set value to the depth of subtree
                memoryMap[memoryMapIndex] = (byte) d;
                depthMap[memoryMapIndex] = (byte) d;
                memoryMapIndex ++;
            }
        }

        subpages = newSubpageArray(maxSubpageAllocs);//new PoolSubpage[2048];
        cachedNioBuffers = new ArrayDeque<ByteBuffer>(8);
    }

首先还是一些赋值操作,并进行一些校验,然后创建了一个memoryMap和depthMap,者两个数组是其实存储的是一个完全二叉树结构,只是完全二叉树结构非常适合数组存储,memoryMap是管理内存的申请,depthMap是树的深度。这颗树的深度为11,最底下一层就是2^11个page,层数越高能分配的内存大小越大,最高层可以分配16M大小。数组内容为[0, 0, 1, 1, 2, 2, 2, 2, 3, 3,…],索引为0的位置没有意义,这样这颗树的父节点下标位置为n则他的左子节点位置就是2n,右子节点位置就是2n+1。
我们继续分析现在poolChunk已经创建好了,内存也已经申请了,下面开始分配

boolean success = c.allocate(buf, reqCapacity, normCapacity);

boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        final long handle;
        if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
            //需要分配的内存大于8K
            handle =  allocateRun(normCapacity);
        } else {
            //需要分配的内存小于于8K,不足一个page
            handle = allocateSubpage(normCapacity);
        }

        if (handle < 0) {
            return false;
        }
        ByteBuffer nioBuffer = cachedNioBuffers != null ? cachedNioBuffers.pollLast() : null;
        initBuf(buf, nioBuffer, handle, reqCapacity);
        return true;
    }

判断申请的大小是不是一个大于一个page,如果大于一个page就不用对page进行切分,如果是小于一个page就需要对page进行切分,这里我们申请的是112B,不足一个page,会走到else方法

private long allocateSubpage(int normCapacity) {
        // Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
        // This is need as we may add it back and so alter the linked-list structure.
        PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);//112  tinySubpagePools[7]
        int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
        synchronized (head) {
            int id = allocateNode(d);//返回查找到的可用节点
            if (id < 0) {
                return id;
            }

            final PoolSubpage<T>[] subpages = this.subpages;
            final int pageSize = this.pageSize;

            freeBytes -= pageSize;//可用内存

            int subpageIdx = subpageIdx(id);//获取使用哪个page
            PoolSubpage<T> subpage = subpages[subpageIdx];
            //前面只是创建数组,如果该位置还没有初始化
            if (subpage == null) {
                //runOffset(id)是计算在chunk中的起始位置,以0开始
                subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
                subpages[subpageIdx] = subpage;
            } else {
                subpage.init(head, normCapacity);
            }
            //正在开始分配
            return subpage.allocate();
        }
    }
PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);

PoolSubpage<T> findSubpagePoolHead(int elemSize) {//默认112
        int tableIdx;
        PoolSubpage<T>[] table;
        //如果分配的是tiny类型
        if (isTiny(elemSize)) { // < 512
            //数组每个位置分配内存大小都是16的倍数
            tableIdx = elemSize >>> 4;//7
            table = tinySubpagePools;
        } else {
            //分配的是small类型
            tableIdx = 0;
            //small数组每个位置大小都是1024的倍数
            elemSize >>>= 10;//除1024
            //找到要分配内存大小在small数组中的位置
            while (elemSize != 0) {
                elemSize >>>= 1;
                tableIdx ++;
            }
            table = smallSubpagePools;
        }
        //然后返回这个PoolSubpage
        return table[tableIdx];
    }

根据规范化后的申请大小获取tiny或者small类型PoolSubpage数组上的PoolSubpage。然后执行

int id = allocateNode(d)//返回查找到的可用节点
private int allocateNode(int d) {//11
        int id = 1;
        int initial = - (1 << d); // has last d bits = 0 and rest all = 1  -2048
        //获取二叉树中的索引为1的位置,
        // 索引为0的位置跳过了,这样索引下标为n的节点子节点位置就是2n和2n+1
        byte val = value(id);//0
        //如果已经被分配了返回-1
        if (val > d) { // unusable 如果被使用直接返回-1
            return -1;
        }
        //如果该值没有被使用或者小于2048及查找到最深一层
        while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
            id <<= 1;//找左子节点
            val = value(id);//获取节点的值
            if (val > d) {//如果本节点已经被使用则遍历兄弟节点
                id ^= 1;//相当于+1操作
                val = value(id);//查看兄弟节点是不是被使用
            }
        }
        byte value = value(id);//找到了该节点
        assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
                value, id & initial, d);
        setValue(id, unusable); // mark as unusable设置该节点已经被使用
        updateParentsAlloc(id);
        return id;
    }

这里其实就是在前面我们说的二叉树上查找可以分配内存的位置信息,如果整棵树都不能分配则直接返回-1,找到这个位置后然后在跟新这棵树,我们来看看更新方法

private void updateParentsAlloc(int id) {
        while (id > 1) {
            int parentId = id >>> 1;//获取父id
            byte val1 = value(id);//该id的深度
            byte val2 = value(id ^ 1);//兄弟节点
            byte val = val1 < val2 ? val1 : val2;//取小的值
            setValue(parentId, val);//更新父节点的val值为兄弟节点
            id = parentId;//继续向上传递这种变化
        }
    }

可以看到这里更新分配节点的父节点的值为兄弟节点,然后不停的向上传递这种变化,其实这么操作之后会发现,每个节点可以分配内存的大小就是memoryMap[index]对应深度可以分配的内存大小。比如这里我们是一颗树高度为2,就是[0,0,1,1,2,2,2,2],我们将下标找到这个最左侧的节点,也就是第一个2的位置将其变为了12,然后向上循环修改后的数组为[0,1,2,1,12,2,2,2],这样也就是说明跟节点能分配的大小为8M,他的左子节点能分配的大小为2M,右子节点可以分配4M大小。我们继续回到allocateSubpage方法中。

更新可用内存大小,从获取上面创建的长度为2048的poolSubpage的数组总获取对应id位置的poolSubpage,如果为null,则创建一个PoolSubpage。我们先跟踪到创建这个PoolSubpage的构造方法中

PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
        this.chunk = chunk;//属于那个chunk
        this.memoryMapIdx = memoryMapIdx;//内存分配管理数组下标位置
        this.runOffset = runOffset;//一段chunk内存的起始位置
        this.pageSize = pageSize;
        //这里最小分配的内存大小为16个字节,一个pageSize是8k,
        //所以最多也就需要8192/16/64=8个long型 表示子page的使用状态,
        //一个long型是64个bit位,也就是最多只需要8个long型就可以表示所有子page的状态了
        bitmap = new long[pageSize >>> 10]; // pageSize / 1024=8
        init(head, elemSize);// 112
    }

void init(PoolSubpage<T> head, int elemSize) {
        doNotDestroy = true;
        this.elemSize = elemSize;//分配的内存大小
        if (elemSize != 0) {
            maxNumElems = numAvail = pageSize / elemSize;//一个page可以创建多少个subPage
            nextAvail = 0;
            bitmapLength = maxNumElems >>> 6;//需要多少个long类型来表示子page的状态
            if ((maxNumElems & 63) != 0) {//如果不足一个long类型则最少为1个
                bitmapLength ++;
            }
            //初始化所有的long类型的bit位全部为0,及全部都是可用状态,每个bit位代表一个子page的状态
            for (int i = 0; i < bitmapLength; i ++) {
                bitmap[i] = 0;
            }
        }
        //将新创建的poolSubPage连接到tinyPoolSubpagePools[]数组上,方便下次分配同样大小的子page
        addToPool(head);
    }

 //head后插入新分配的子page
    private void addToPool(PoolSubpage<T> head) {
        assert prev == null && next == null;
        prev = head;
        next = head.next;
        next.prev = this;
        head.next = this;
    }

这里会赋值当前的poolSubpage是属于那个chunk,然后赋值内存分配管理数组下标位置,在这个chunk中的内存起始位置,赋值page的大小,然后创建一个长度为8的位图。然后执行init方法,计算一个page最多可以分配成多少个subpage,然后计算需要多少个long类型来表示这些subpage是不是被使用的状态,如果不足一个则最少需要一个,然后将这个位图全部初始化为0,然后将这个poolSubpage加到上面创建的tinyPoolSubpagePools,并且新的节点连接到head后面,这样如果创建同样大小的内存时就可以直接申请。继续回到allocateSubpage方法,将申请到的subpage赋值到subpages数组上,然后继续分配操作。

long allocate() {
        //如果分配大小是0直接返回
        if (elemSize == 0) {
            return toHandle(0);
        }
        //如果可分配的子page或者已经销毁直接返回-1
        if (numAvail == 0 || !doNotDestroy) {
            return -1;
        }
        //获取bitmap中可用的id,bitmap的低6位也就是[0-63]表示的是分配的是第几个subpage,
        //剩下的高位表示在bimap中的数组下标,也就是[0-7],这样就可以通过一个int值来表示,
        //subpage的占用情况,比如我们分配的是16B,现在分配到了第450个subpage,则
        //bitmapIdx的值为111|000001,其实就是449,表示分配的就是第450个subpage
        final int bitmapIdx = getNextAvail();
        //除以64(bitmap的相对下标)
        int q = bitmapIdx >>> 6;
        //除以64取余, 其实就是当前绝对id的偏移量
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) == 0;
        //当前位标记为1
        bitmap[q] |= 1L << r;
        //如果可用的子page为0
        //可用的子page-1
        if (-- numAvail == 0) {
            //子page已经完全使用完了,从链表移除
            removeFromPool();
        }
        //bitmapIdx转换成handle
        return toHandle(bitmapIdx);
    }

这里首先是一些判断,然后从位图中获取可用的位置,我们跟踪到getNextAvail方法。

private int getNextAvail() {
        int nextAvail = this.nextAvail;
        if (nextAvail >= 0) {
            //一个子page被释放之后, 会记录当前子page的bitmapIdx的位置, 下次分配可以直接通过bitmapIdx拿到一个子page
            this.nextAvail = -1;
            return nextAvail;
        }
        return findNextAvail();
    }

 private int findNextAvail() {
        //当前long数组
        final long[] bitmap = this.bitmap;
        //long数组的长度
        final int bitmapLength = this.bitmapLength;
        //遍历所有的long数组获取可用的bitMapIdx
        for (int i = 0; i < bitmapLength; i ++) {
            long bits = bitmap[i];
            if (~bits != 0) {//如果没有全部被使用,则从当前的long类型所代表的子page中查找
                return findNextAvail0(i, bits);
            }
        }
        return -1;
    }

private int findNextAvail0(int i, long bits) {
        final int maxNumElems = this.maxNumElems;//分成多少个子page
        final int baseVal = i << 6;//根据传入的第几个long获取起始位置

        for (int j = 0; j < 64; j ++) {
            //如果是2的倍数,一直向右移动找到第一个为0的位置
            if ((bits & 1) == 0) {
                //这里相当于加, 将i*64之后加上j, 获取绝对下标
                int val = baseVal | j;
                //小于块数,不能越界
                if (val < maxNumElems) {
                    return val;
                } else {
                    break;
                }
            }
            //右移一位
            bits >>>= 1;
        }
        return -1;
    }

这里就是在查询位图那个位置可以使用。找到这个位置后,将这个bit位标记为1,也就代表这个位置被占用了,然后可用位置减一,如果可用位置为0,则将这个subpage从上面说的tinyPoolSubpagePools移除

private void removeFromPool() {
        assert prev != null && next != null;
        prev.next = next;
        next.prev = prev;
        next = null;
        prev = null;
    }

然后将将bitmapIdx转换成handle

private long toHandle(int bitmapIdx) {
        //这个0x4000000000000000L是高位为1其他位置都为0,目的就是为了第一个分配的时候为0,或操作之后不为0,
        //这样在init初始化的时候就可以通过bitmapIdx==0来判断是不是分配的是不是超过一个page了
        return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
    }

这里可以看到是用一个long型表示,0x4000000000000000L是一个最高位为1其他位置全部为0的二进制数,这里高32为表示在位图中的位置,低32位表示二叉树内存分配的位置,其实就可以唯一确定这块内存的位置。然后我们回到io.netty.buffer.PoolChunk#allocate方法中,调用initBuf方法

initBuf(buf, nioBuffer, handle, reqCapacity);

 void initBuf(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity) {
        int memoryMapIdx = memoryMapIdx(handle);//取低32位
        int bitmapIdx = bitmapIdx(handle);//取高32位
        if (bitmapIdx == 0) {//如果分配的内存大于一个page
            byte val = value(memoryMapIdx);
            assert val == unusable : String.valueOf(val);
            buf.init(this, nioBuffer, handle, runOffset(memoryMapIdx) + offset,
                    reqCapacity, runLength(memoryMapIdx), arena.parent.threadCache());
        } else {
            initBufWithSubpage(buf, nioBuffer, handle, bitmapIdx, reqCapacity);
        }
    }

private void initBufWithSubpage(PooledByteBuf<T> buf, ByteBuffer nioBuffer,
                                    long handle, int bitmapIdx, int reqCapacity) {
        assert bitmapIdx != 0;

        int memoryMapIdx = memoryMapIdx(handle);//二叉树中分配的那个page

        PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];//获取子page
        assert subpage.doNotDestroy;
        assert reqCapacity <= subpage.elemSize;
		
        buf.init(
            this, nioBuffer, handle,
            runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset,//runOffset(memoryMapIdx): page在chunk中的偏移量
                //(bitmapIdx & 0x3FFFFFFF): 子page是属于第几个子page
                //(bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset:表示在当前page的偏移量
                //offset:0
                reqCapacity, subpage.elemSize, arena.parent.threadCache());
    }

这里获取handle的高32位和低32位的值,然后判断bitmapIdx是否为0,为0说明是分配内存大于一个page,否则申请的内存大小小于一个page,然后开始申请就是对个pooledByteBuf进行赋值,这个byteBuf的在这个chunk中的起始偏移位置,申请的内存大小,和subpage的大小,所属的PoolThreadCache等信息。

void init(PoolChunk<T> chunk, ByteBuffer nioBuffer,
              long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
        init0(chunk, nioBuffer, handle, offset, length, maxLength, cache);
    }

private void init0(PoolChunk<T> chunk, ByteBuffer nioBuffer,
                       long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
        assert handle >= 0;
        assert chunk != null;

        this.chunk = chunk;//属于那个chunk
        memory = chunk.memory;//chunk中的内存
        tmpNioBuf = nioBuffer;
        allocator = chunk.arena.parent;//缓冲区分配器
        this.cache = cache;//PoolThreadCache
        this.handle = handle;//long类所代表子page是否使用 和page位置组成
        this.offset = offset;//chunk中的偏移量,可以定位到每块位置,读写基于该偏移量
        this.length = length;//目标申请内存大小
        this.maxLength = maxLength;//最长可分配的长度,这个与byteBuf扩容有关系
    }

这样就创建一个byteBuf,这个byteBuf表示了这块连续内存的位置,我们针对这个byteBuf的操作就是根据这段内存来进行的。我们再回到io.netty.buffer.PoolArena#allocateNormal方法

qInit.add(c);

void add(PoolChunk<T> chunk) {
        if (chunk.usage() >= maxUsage) {
            nextList.add(chunk);
            return;
        }
        add0(chunk);
    }

void add0(PoolChunk<T> chunk) {
        chunk.parent = this;
        if (head == null) {
            head = chunk;
            chunk.prev = null;
            chunk.next = null;
        } else {
            chunk.prev = null;
            chunk.next = head;
            head.prev = chunk;
            head = chunk;
        }
    }

将上面创建的chunk根据使用率添加到对应的chunkList中,下次申请内存的时候就可以在chunkList双向链表中查询chunk进行内存分配了。回到io.netty.buffer.PoolArena#allocate()方法,记录申请了多少个tiny或small类型。

incTinySmallAllocation(tiny);
private void incTinySmallAllocation(boolean tiny) {
        if (tiny) {
            allocationsTiny.increment();
        } else {
            allocationsSmall.increment();
        }
    }

我们来看看normal类型的分配

if (normCapacity <= chunkSize) {
            if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
                // was able to allocate out of the cache so move on
                return;
            }
            synchronized (this) {
                allocateNormal(buf, reqCapacity, normCapacity);
                ++allocationsNormal;
            }
        }

这里同样是先从每个线程的缓存中分配,如果分配失败再直接分配,我们这里先跳过缓存分配,下面会在介绍,跟踪到allocateNormal方法,我们上面也就分析过了,会走到io.netty.buffer.PoolChunk#allocate方法中

if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
            //需要分配的内存大于8K
            handle =  allocateRun(normCapacity);
        } 

这里申请的内存大于一个page所以会执行allocateRun方法,我们继续跟踪进去

private long allocateRun(int normCapacity) {
        int d = maxOrder - (log2(normCapacity) - pageShifts);//获取分配大于8K的内存深度
        int id = allocateNode(d);//获取在二叉树中可以分配的位置
        //如果已经分配满了直接返回
        if (id < 0) {
            return id;
        }
        //修改可使用长度
        freeBytes -= runLength(id);
        return id;
    }

然后会走到io.netty.buffer.PoolChunk#initBuf方法,这个方法我们在上面也有介绍,这里bitmapIdx==0走到if逻辑

if (bitmapIdx == 0) {//如果分配的内存大于一个page
            byte val = value(memoryMapIdx);
            assert val == unusable : String.valueOf(val);
            buf.init(this, nioBuffer, handle, runOffset(memoryMapIdx) + offset,
                    reqCapacity, runLength(memoryMapIdx), arena.parent.threadCache());
        }

和上面分析的过程类型,就是获取一个pooledByteBuf然后赋值开始的偏移量位置,所属的PoolThreadCache,

申请长度,最大长度等值。然后就可以使用了。

huge类型内存申请,huge类型大小超过了16M,就不会通过池化分配而是直接申请

else {
            // Huge allocations are never served via the cache so just call allocateHuge
            //分配huge对象,超过了16M
            allocateHuge(buf, reqCapacity);
        }

private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
        //申请所需大小内存
        PoolChunk<T> chunk = newUnpooledChunk(reqCapacity);
        activeBytesHuge.add(chunk.chunkSize());
        //huge类型,不会池化直接初始化
        buf.initUnpooled(chunk, reqCapacity);
        allocationsHuge.increment();
    }

void initUnpooled(PoolChunk<T> chunk, int length) {
        init0(chunk, null, 0, chunk.offset, length, length, null);
    }

    private void init0(PoolChunk<T> chunk, ByteBuffer nioBuffer,
                       long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
        assert handle >= 0;
        assert chunk != null;

        this.chunk = chunk;//属于那个chunk
        memory = chunk.memory;//chunk中的内存
        tmpNioBuf = nioBuffer;
        allocator = chunk.arena.parent;//缓冲区分配器
        this.cache = cache;//PoolThreadCache
        this.handle = handle;//long类所代表子page是否使用 和page位置组成
        this.offset = offset;//chunk中的偏移量,可以定位到每块位置,读写基于该偏移量
        this.length = length;//目标申请内存大小
        this.maxLength = maxLength;//最长可分配的长度,这个与byteBuf扩容有关系
    }

到这里我们就分析完了不通过线程本地分配的情况了

第二章 深入理解 Netty ByteBuf:组成、读写、内存释放与头尾释放源码解析 摘要: 本文深入解析Netty中的ByteBuf核心机制,涵盖其结构设计、读写操作、内存管理和释放策略。ByteBuf通过分离读写指针解决ByteBuffer的缺陷,支持堆/堆外内存及池化分配。文章详细介绍了写入、读取、查看操作的方法与注意事项,强调引用计数机制在内存释放中的关键作用,并对比分析了discardReadBytes()和clear()的底层实现差异。最后通过Echo服务器示例演示实践应用,帮助开发者高效使用ByteBuf并避免内存泄漏。 阅读详情

相关推荐

从符号到基元:Token的技术演化史与“词元”汉化的术语学

中文摘要:Token概念演化与"词元"译名的确立 本文系统梳理了"Token"概念从信息论符号到AI基元的完整演化史,揭示了"词元"这一中文译名确立的必然性。概念发展经历了三个阶段:早期作为编译原理中的词法单元(1960s),统计NLP时期作为概率空间的原子(1990s-2000s),以及神经时代成为动态语义载体(2010s后)。随着Transformer架构兴起,Token从静态符号转变为上下文相关的语义基元,其定义彻底突破"词&quo

AllenLV的博客 1220

【JAVA 网络编程系列】Netty -- 内存管理(池化内存)

【JAVA 网络编程系列】Netty -- 内存管理(池化内存) 【1】jemalloc 简介 【1.1】内存池简介 内存池是指应用程序向操作系统(或 JVM)申请一块内存,对象的创建和销毁都从这块内存中分配和回收,对应地,管理这块内存的工具就称作内存分配器;对于申请对象的不同可以分为堆内存池(向 JVM 申请的内存)和直接内存池(向操作系统申请的内存); 业界著名的三种内存分配器 ptmalloc,Doug Lea 编写的分配器,支持每个线程(per-thread,简称 pt)的 arena,gl

CopperSun 技术博客 1950

ESP32Servo库驱动SG90舵机:从入门到精准控制

本文详细介绍了如何使用ESP32Servo库驱动SG90舵机,从硬件连接到软件环境搭建,再到编写示例程序实现精准控制。内容涵盖库安装、基础驱动代码解析、舵机校准方法以及平滑运动控制,帮助开发者快速掌握ESP32控制舵机的核心技能,并提供了电源管理、信号抗干扰等实用避坑指南。

ik678901的博客 895

Netty内存池化(三)真实内存池

真实内存指的是ByteBuf底层的array和DirectByteBuffer,真实内存池化的好处如下: 降低真实内存的分配开销。因为DirectByteBuffer的分配很抵效(创建堆外内存的速度比堆内存慢了10到20倍),特别的对于DirectByteBuffer的分配开销降低尤为明显。 因为真实内存可以复用,避免了大量对象的回收,降低了GC频率。 怎么降低线程竞争的 默认有核数*2个PoolArea,每个线程初始绑定一个最少线程使用的PoolArea,线程一旦绑定了对应的PoolArea,不能再

傅红雪的专栏 1275

opencv-meshgrid

使用opencv::repeat函数和std::iota函数完成meshgrid功能。 X Y Z 通俗的说就是根据Z的大小生成X矩阵,Y矩阵

yuegooxi的博客 1607

LC整流滤波电路DIY:电感电容配合,手把手搭建低纹波直流电源

在直流电源设计中,整流滤波是连接交流输入与稳定输出的关键环节。常见的电容滤波虽然简单,但在大负载下纹波抑制能力有限,电感抵抗电流突变、电容抵抗电压突变,二者组合构成的LC滤波能有效降低输出电压纹波,同时减小开机浪涌电流。在工频线性电源、功放电源等对噪声敏感的场景中,LC滤波常作为稳压前的平滑缓冲。本文以一套完整DIY项目为线索,介绍整流桥后串联功率电感、并联滤波电容的电路构成,结合实际计算与波形测试,讨论电感量估算、电容选取、谐振风险、饱和电流等工程问题,并给出上电测试和故障排查方法。通过搭建与调试,可直观

weixin_29168813的博客 375

(一)Netty必学知识点 netty基础知识点总结

 Java读源码之Netty深入剖析解析netty各大组件细节/百万级性能调优/设计模式实际运用 前言:这是一门对Java开发人员非常重要的课程,源码的学习方式是不可逃避的。Netty也是大型互联网公司面试必备的问题,如果没有分布式开发经验,在面试时提出自己阅读过Netty源码,并能清晰表达的话。这部分内容会是很重要的加分项 nioEventloop执行流程 Netty 源码阅读的思考---...

很多时候犯错都是在不知情的情况下发生的 4653

华为OD机试真题 新系统 2026-04-19 C语言 实现【8位LED控制器】

有一个8位LED控制器,包含8个LED灯(编号0-7),初始状态全灭,用8位二进制表示为:00000000。控制器可以接收以下三种指令: Lx:L表示点亮操作,x表示LED的编号(0一7),操作得到的结果是:点亮第x个LED灯,把状态设为1。 Dx:D表示熄灭操作,x表示LED的编号(0-7),操作得到的结果是:熄灭第x个LED灯,把状态设为0。 Tx:T表示切换操作,x表示LED的编号(0-7),操作得到的结果是:切换第x个LED灯的状态,若状态为0则变为1,为1则变为0。 现在给定一组指令字符串(0<

MISAYAONE的博客 146

Netty学习——源码篇12 Netty池化内存管理机制

首先调用leastUsedArena()方法分别获得类型为PoolArena的heapArena和directArena对象。那么heapArena和directArena对象是在哪里初始化呢?其实就是创建了一个固定大小的PoolArena数组,数组大小由传入的参数nHeapArena和nDirectArena决定。发现nHeapArena和nDirectArena是通过DEFAULT_NUM_HEAP_ARENA, DEFAULT_NUM_DIRECT_ARENA 这两个常量默认赋值的。

geminigoth的博客 2380

PCB板设置20H 原则理解

20H原则是PCB设计中抑制EMI辐射的关键方法,要求电源层比相邻地层内缩20倍介质厚度(20H)。在四层板中实现20H原则需采用S-G-P-S层叠结构,确保GND与PWR紧邻。具体步骤包括:确定GND-PWR间介质厚度H,计算20H值(通常80-120mil),并在设计软件中将电源层四周内缩20H,同时保持地层完整不内缩。注意避免常见误区,如混淆介质厚度、地层内缩等。该原则能有效限制边缘场辐射,降低EMI干扰,是四层板EMC设计的重要规范。

qq_37999850的博客 688

Apollo Planning决策规划算法代码详细解析 (2):Scenario执行

上一章节讲Scenario的决策逻辑,当确认当前Scenario后,本章节继续深入讲解在代码中,Scenario的执行过程。Scenario的Process()函数根据配置文件顺序执行stage,并判断目前处于哪个stage。当确认好stage后调用该stage对应的Process()函数,来进行具体的规划任务。 码字不易,喜欢的朋友们麻烦点个关注与赞。 在本文你将学到下面这些内容: Scenario的Process()函数的作用于调用方式; Scenario对象的初始化方法; 如何给每个Scen

自动驾驶Player的博客 5608

netty源码浅析-池化内存回收

这里根据bitmapIdx计算在位图数组中的位置,然后将bit为重新设置为0,也就是没有被占用状态,然后设置下一个可用位置就是当前释放的位置,如果可用大小和最大可分配内容不相同则直接返回,说明这个poolSubpage还在被占用,如果相等,查看是不是就一个head节点,如果是也直接返回,如果不是可能是别的poolSubpage加入了pool,则将释放的subpage移除。直接内存通过两种方式释放堆外内存,一种是使用cleaner执行clean方法释放内存,一种是执行unsafe方法释放内存。

GeekerJava的博客 341

DevExpress中使用BandGridView实现复合(多行)表头、设置多行表头背景颜色、表格边框颜色

一、实现效果 二、 实现方法 2.1、创建复合表头 ①将创建的GridControl下的GirdView1转化为BandGridView类型; ②创建需要展示的列(指定列的名称【Name】、描述【caption】、数据字段名称【FieldName】) ③绑定列实现复合表头。 注意:如果复合表头有多行,则需要设置新增Band的Row Count,然后设置【Band properties下的AutoFillDown=false】 详细操作如下: 注意:如果要隐藏表格的设计字段内容,则需.

提供C#相关开发 、云计算、运维测试、网络安全相关技术分享与服务,有意向可联系。 2893

netty源码浅析-池化内存PoolThreadCache分配

其实PoolArena中的中的数组,如tinySubpagePools中的元素其实也是双向链表,他会把我们第一次申请的元素连接到数组中的head下,这样我们如果在下次需要申请和之前申请的长度相同时,就可以快速找到相同大小的已经分配的poolChunk,然后就可以直接通过这个page已经切分后的subpage进行分配了。这里我们看下分配后的大小分类说明。上面我们已经分析了没有命中缓存的分配方式,会分配一段连续的内存来分配,现在我们来分析一下名字缓存的分配方式,这个实现就在PoolThreadCache中。

GeekerJava的博客 177

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

netty源码浅析-byteBuf介绍

byteBuf byteBuf介绍 在介绍netty的byteBuf之前我们先来回顾一下nio的byteBuffer,byteBuffer内部维护了一个byte[]数组,而且提供了几个属性position、limit、capacity、mark四个属性来组织byteBuffer。 position:当前操作的位置 limit:最大可以读或者可写的位置 capacity:最大的空间容量是多少,byteBuffer在创建时,就固定下来 mark:标记当前的位置,下次可以回到这个位置 ByteBuffer b

GeekerJava的博客 172

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Netty源码浅析

Netty源码及执行流程

Java_dragon95的博客 709

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

netty源码浅析-PooledByteBuf扩容操作

在AbstractByteBuf中会有很多调用ensureWritable0的操作,判断需不需要扩容,我们跟踪到这个方法中,如果需要写入的空间比可写内存小则直接返回,不需要扩容,如果不是则重新计算需要扩容的长度。这里会先判断需要扩容的大小不能超过最大值,如果大小超过了4M,则每次向上取整获取扩容的大小,如果小于4M,则以64开始每次翻一倍,获取大于minNewCapacity的扩容值。我们上面分析了分配操作和回收操作,部分代码我们在上面已经分析过了,现在我们看下扩容操作。

GeekerJava的博客 249

Netty学习笔记内存管理篇:直接内存和堆内存

目录 直接内存和堆内存 堆内存 直接内存(堆外内存) 零拷贝 NIO原生直接内存 直接内存的GC Netty的直接内存 在介绍Netty的内存管理前,先简单了解一下直接内存和堆内存 直接内存和堆内存 jvm中使用的内存可分为两种,一个是堆内存,一个是直接内存。 堆内存(HeapByteBuf)字节缓冲区:特点是内存的分配和回收速度快,可以被JVM自动回收;缺点就是如果进行So...

船长 6286

Netty 内存池申请内存流程

该文所涉及的 netty 源码版本为 4.1.16。

黎明就要到来 564

Netty源码分析-Channel对Buffer的堆内存和直接内存的使用

Channel的Unsafe对Buffer的使用 在Channel的接口体系设计中,Channel只是与ChannelHandler,ChannelHandlerContext,ChannelPipeline完成对数据的处理,而不直接参与和底层socket进行数据交互,是通过一个内部Unsafe的接口设计来完成这项工作。 如对于数据读取,在Channel的实现中拿到的是Unsafe已经读取回来的...

源码之下,了无秘密 6419

【Netty4】直接内存

文章目录1. 什么是直接内存2. 直接内存分配源码分析3. 使用直接内存的优缺点: 1. 什么是直接内存 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,某些情况下这部分内存也会被频繁地使用,而且也可能导致OutOfMemoryError异常出现。 Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间对应的内存也叫作直接内存,它们对应的都是机器的物理内存。 package com.test; import

m0_45406092的博客 936

netty——bytebuf的创建、内存分配与池化、组成、扩容规则、写入读取、内存回收、零拷贝

文章目录ByteBuf创建内存分配与池化ByteBuf组成扩容规则写入读取内存回收零拷贝ByteBuf优势 ByteBuf ByteBuf是对nio中ByteBuffer的增强。主要的增强点就是ByteBuf它可以动态调整容量大小,当要存储的数据超过了当前容量的上限就会进行扩容,扩容的上限是多少?扩容机制是什么?请跟着本文往下看。对了,还有一个增强就是byteBuf不用和ByteBuffer一样进行读写模式的切换,ByteBuffer中读与写是共用一个指针,而ByteBuf既有读指针,也有写指针。 创建

qq_44027353的博客 4659

Spring AI + GPT-5.5 + Ollama 配置教程:Base URL、超时与流式调用

由于不同 Spring AI、Spring Boot 小版本之间仍可能存在差异,创建项目时应检查 Spring Initializr、Spring AI BOM 和官方兼容矩阵,不要直接复制旧教程中的 Starter 名称和版本号。多人项目最好隔离不同客户端,并在脱敏前提下记录最终 URL、状态码、请求耗时、请求 ID 和模型 ID。Spring AI 接入模型 API,至少要同时确认 Starter、Base URL、请求路径、API Key、模型 ID、连接超时、读取超时和流式客户端这 8 项。

2601_95893075的博客 215

V8 机制、手写核心函数与 TypeScript 类型体操

或固定初始化(如数组先定长、对象先给全字段)保持类型与形状稳定;这些都比"换框架"更基础。只能处理纯数据(丢函数、Date、RegExp、循环引用报错)。生产级深拷贝要处理:原始值短路、循环引用、V8(Chrome / Node 的 JS 引擎)不是"解释一行跑一行"那么简单,它用。图:V8 执行流程——解释执行 + 热点优化,优化失败会 Deopt 回退。JS 内存自动管理,但"自动"不等于"不会漏"。是"固定间隔最多执行一次"。手写 Promise 是"懂异步"的试金石。是"停下后才执行最后一次",

qq_38468358的博客 326

【JVM】对象内存布局

HotSpot JVM 中,Java 对象内存布局由对象头(12字节,含Mark Word与压缩Klass Pointer)、实例数据及对齐填充构成,总大小为8字节倍数。对象头支持锁状态动态复用,指针压缩使引用仅4字节,但堆超32GB时失效。实例字段按类型优先级重排序以减少间隙,最小对象大小为16字节。通过JOL工具可精准验证布局,理解锁机制、GC、伪共享等底层行为。

2301_79732484的博客 140

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)

要努力去发光,而不是被照亮~ 112
上一篇: 设计模式-23职责链模式
下一篇: netty源码浅析-池化内存回收
胖柯G
博客等级 码龄10年 453粉丝 57原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值