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

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

  • 相关阅读:
    xp系统优化
    项目开发文档格式13种 (转载)
    java 操作 ORACLE
    orclae temp table
    把EXCEL上传并BINDING到GRIDVIEW中
    从excel读数据写入数据库代码
    GMDatePicker控件的使用
    代码汇总
    dwr运行时出现Servlet.init() for servlet dwrinvoker threw exception的解决方法
    CVSNT安装
  • 原文地址:https://www.cnblogs.com/allenwas3/p/12350442.html
Copyright © 2011-2022 走看看