zoukankan      html  css  js  c++  java
  • netty 内存相关,太复杂了

    netty 默认使用池化,堆外内存

    // 创建内存分配器,使用池化,堆外。正常使用 netty 时,不需要自行创建
    PooledByteBufAllocator allocator = new PooledByteBufAllocator(true);
    // 分配内存
    ByteBuf buffer = allocator.buffer(512);
    // 归还内存
    buffer.release();

    内存规格
    512B,8K
    < 512B        tiny
    521B ~ 8k   small
    >8K             normal

    起始在这

    PooledByteBufAllocator

    为了避免多线程竞争,使用 PoolThreadCache,每个线程有单独的内存区域,为简化说明,这里只考虑堆外内存。

    final class PoolThreadCache {
        // 内存分配之后,被释放,则先放到缓存
        final PoolArena<ByteBuffer> directArena;
        private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
        private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
        private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
    }

    简单来看,PoolArena 组合了 PoolChunk 和 PoolSubpage

    abstract class PoolArena<T> implements PoolArenaMetric {
        private final PoolSubpage<T>[] tinySubpagePools;
        private final PoolSubpage<T>[] smallSubpagePools;
        
        private final PoolChunkList<T> q050;
        private final PoolChunkList<T> q025;
        private final PoolChunkList<T> q000;
        private final PoolChunkList<T> qInit;
        private final PoolChunkList<T> q075;
        private final PoolChunkList<T> q100;
    }

    PoolChunk
    一个 chunk 16M
    一个 page 8K
    利用一棵完全二叉树,把连续的 16M 内存分段,不同深度的节点代表不同粒度大小的内存。

    用数组表示完全二叉树,数组有 4096 个元素,第 0 个元素不使用,用 1-4095 来对应完全二叉树的节点

    这个数组就是 byte[] memoryMap,数组元素的值是深度,树的深度和可分配内存的大小有关联。
    memoryMap[1] = 0
    memoryMap[2] = 1
    memoryMap[3] = 1
    memoryMap[4] = 2
    memoryMap[5] = 2
    memoryMap[6] = 2
    memoryMap[7] = 2


    深度 -> 内存
    0 -> 16M
    1 -> 8M
    2 -> 4M
    3 -> 2M
    4 -> 1M
    5 -> 512K
    6 -> 256K
    7 -> 128K
    8 -> 64K
    9 -> 32K
    10 -> 16K
    11 -> 8K


    根据申请内存大小,算出二叉树的深度,如果申请 8K 内存,则从 11 层寻找节点
    第一次分配,把 2048 号节点分配出去,然后更新 2048 号节点父节点 1024 号 ,memoryMap[1024] = 11,表示该节点只能分配 8K 大小的内存了

    // io.netty.buffer.PoolChunk#allocate
    boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        final long handle;
        // 申请的内存,规格化之后大于 8K
        if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
            handle =  allocateRun(normCapacity);
        } else {
            handle = allocateSubpage(normCapacity);
        }
    
        if (handle < 0) {
            return false;
        }
        ByteBuffer nioBuffer = cachedNioBuffers != null ? cachedNioBuffers.pollLast() : null;
        initBuf(buf, nioBuffer, handle, reqCapacity);
        return true;
    }
    
    private long allocateRun(int normCapacity) {
        // 完全二叉树的深度
        int d = maxOrder - (log2(normCapacity) - pageShifts);
        // 节点的序号
        int id = allocateNode(d);
        if (id < 0) {
            return id;
        }
        freeBytes -= runLength(id);
        return id;
    }

    PoolSubpage
    小于 8K 的分配,有

    tinySubpagePools 的规格分别为 16, 32, 64, ... ,496,共 32 个元素,最后一个元素应该没有用到

    smallSubpagePools 规格分为 512, 1024, 2048, 4096,共 4 个元素

    16M 内存按 8K 分页,
    申请的内存大于 8K,直接分配多个连续的页;
    申请的内存小于 8K,则把页按规格分段,small 分为 512,1024,2048,4096,tiny 分为 16,32,64,96,。。。
    需要明确的是,一个内存页只能分配固定规格的内存片段


    分配内存的最小单位是 16 字节,一页大小 8K,所以最多需要 8K/16=512 位来标识内存是否分配,即 8 个 long 型。


    分配一页,怎么标记一页已被分配?
    二叉树数组 memoryMap 的元素值,深度改变了,表明该页被分配。

    分配一小段,怎么标记一小段已被分配?
    用 8 个 long,一位表示一段被分配。

    小块内存的释放,把内存放到 cache 的队列中

    io.netty.buffer.PoolThreadCache.MemoryRegionCache#add

    再次分配内存时,从缓存中取

    io.netty.buffer.PoolThreadCache.MemoryRegionCache#allocate

    内存的管理,非常复杂,后面再慢慢看吧!

  • 相关阅读:
    记第一场省选
    POJ 2083 Fractal 分形
    CodeForces 605A Sorting Railway Cars 思维
    FZU 1896 神奇的魔法数 dp
    FZU 1893 内存管理 模拟
    FZU 1894 志愿者选拔 单调队列
    FZU 1920 Left Mouse Button 简单搜索
    FZU 2086 餐厅点餐
    poj 2299 Ultra-QuickSort 逆序对模版题
    COMP9313 week4a MapReduce
  • 原文地址:https://www.cnblogs.com/allenwas3/p/12350442.html
Copyright © 2011-2022 走看看