Netty学习笔记内存管理篇:内存分配ByteBufAllocator

此接口来进行实际的内存分配,默认使用的是ByteBufAllocator.DEFAULT,初始化时会根据配置和平台进行赋值。

io.netty.allocator.type可以设置为unpooled和pooled指定是否需要缓冲池,如果不设置则会根据平台判断。逻辑如下:

static {
    String allocType = SystemPropertyUtil.get(
            "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
    allocType = allocType.toLowerCase(Locale.US).trim();

    ByteBufAllocator alloc;
    if ("unpooled".equals(allocType)) {
        alloc = UnpooledByteBufAllocator.DEFAULT;
    } else if ("pooled".equals(allocType)) {
        alloc = PooledByteBufAllocator.DEFAULT;
    } else {
        alloc = PooledByteBufAllocator.DEFAULT;
    }
    DEFAULT_ALLOCATOR = alloc;
}

AbstractByteBufAllocator继承此接口,实现了一些通用的逻辑,比如最基本的buffer方法。

@Override
public ByteBuf buffer() {
    if (directByDefault) {
        return directBuffer();
    }
    return heapBuffer();
}

根据directByDefault属性来判断创建不同类型的buffer,至于directByDefault属性的赋值,在此抽象类中并没有赋值操作。
经过一系列的重载之后调用了如下两个抽象方法:

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

它们的实现在UnPooledByteBufAllocator或者PooledByteBufAllocator中,我们先看前者的实现逻辑。

UnPooledByteBufAllocator

该分配器分配的Bytebuf底层为不使用对象池技术字节数组。至于直接内存还是堆内存取决于参数,至于是否通过Unsafe分配取决于配置以及平台,这里多说一点,一般创建对象通过new关键字,即三条指令创建对象,反射也一样。其实Unsafe/safe分配器对于ByteBuf的创建没有多大的差异,只是写入数据的效率Unsafe是通过内存地址直接写入,会更快一些。

从它的构造函数入手:

public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector, boolean tryNoCleaner) {
    super(preferDirect);
    this.disableLeakDetector = disableLeakDetector;
    noCleaner = tryNoCleaner && PlatformDependent.hasUnsafe()
            && PlatformDependent.hasDirectBufferNoCleanerConstructor();
}

第一个参数表示是否为直接内存,第二个为是否开启内存泄漏检测,第三个参数由平台决定的,表明Netty是否参与直接内存的回收。
其中还调用了父类的构造方法:

protected AbstractByteBufAllocator(boolean preferDirect) {
    directByDefault = preferDirect && PlatformDependent.hasUnsafe();
    emptyBuf = new EmptyByteBuf(this);
}

主要是对前面刚提到的directByDefault属性进行赋值。UnPooledByteBufAllocator对newDirectBuffer和newHeapBuffer都进行了实现。

PooledByteBufAllocator

Netty实际使用内存分配器会根据配置采用PooledByteBufAllocator.DEFAULT,所有事件循环线程使用的是一个分配器实例。PooledByteBufAllocator将内存分为PoolArena,PoolChunk和PoolPage,Chunk中包含多个内存页,Arena包含3个Chunk。
先从重要的常量看起。
 

// 默认堆内存类型PoolArena个数
private static final int DEFAULT_NUM_HEAP_ARENA;
// 默认直接内存类型PoolArena个数
private static final int DEFAULT_NUM_DIRECT_ARENA;
// 默认页大小
private static final int DEFAULT_PAGE_SIZE;
// 每个chunk中的page是用平衡二叉树映射管理每个PoolSubpage是否被分配
// maxOrder为树的深度,深度为maxOrder层的节点数量为1 << maxOrder,maxOrder =< 11
private static final int DEFAULT_MAX_ORDER; 
 //默认的tiny cache 的大小 512
private static final int DEFAULT_TINY_CACHE_SIZE;
 //默认的small cache的大小 256
private static final int DEFAULT_SMALL_CACHE_SIZE;
//默认的normal cache的大小 64
private static final int DEFAULT_NORMAL_CACHE_SIZE;
private static final int MIN_PAGE_SIZE = 4096;
//最大Chunk的大小,默认等于2的30次方 即1G
private static final int MAX_CHUNK_SIZE = (int) (((long) Integer.MAX_VALUE + 1) / 2);

在静态代码块中对这些常量进行初始化操作。PooledByteBufAllocator内部有两个重要数组HeapArena和DirectArena,用来记录堆内存和直接内存当前的使用状态。PoolArena都实现了PoolArenaMetric接口,用于测量内存使用状况。数组的长度即为上面的定义的常量。除此之外,还有一个重要的对象PoolThreadLocalCache,其继承了FastThreadLocal,用于线程的本地缓存,在内存管理中,线程本地内存缓区的信息会保存在PoolThreadCache对象中。PooledByteBufAllocator覆盖的newHeapBuffer和newDirectBuffer用来分配内存。构造函数中对成员变量进行过初始化操作。
 

public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
                              int tinyCacheSize, int smallCacheSize, int normalCacheSize,
                              boolean useCacheForAllThreads, int directMemoryCacheAlignment) {
    super(preferDirect);
    // PoolThreadLocalCache继承自FastThreadLocal,用来保存PoolThreadCache
    threadCache = new PoolThreadLocalCache(useCacheForAllThreads);
    this.tinyCacheSize = tinyCacheSize;
    this.smallCacheSize = smallCacheSize;
    this.normalCacheSize = normalCacheSize;
    // chunkSize = DEFAULT_PAGE_SIZE(8KB) << DEFAULT_MAX_ORDER(11)
    chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);
    // 检查pageSize是否大于4K且为2的幂次方,如果不是则抛异常,返回的值为pageSize二进制的尾部0的个数 
    // pageSize如果为是8192时它的二进制表示是10000000000000,那么这个pageShifts就是13
    int pageShifts = validateAndCalculatePageShifts(pageSize);
    // 省略HeadArena的构造... 
    if (nDirectArena > 0) {
        // 创建DirectArena数组
        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;
            // arena实现了PoolArenaMetric,也添加到指标列表中
            metrics.add(arena);
        }
        // 返回只读的列表
        directArenaMetrics = Collections.unmodifiableList(metrics);
    }
    // PooledByteBufAllocator的测量指标
    metric = new PooledByteBufAllocatorMetric(this);
}

下面继续分析对newHeadpBuffer的实现:

@Override
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
    // 获取到PoolThreadLocalCache
    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中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值