(三) netty非池化内存的分配与回收:UnpooledByteBufAllocator

文章目录

  • 1. 引子
  • 2. 整体逻辑
  • 3. UnpooledByteBufAllocator
  • 4. 内存增长策略

1. 引子

在上一篇文章
(二) netty非池化内存的表示:UnpooledHeapByteBuf和UnpooledDirectByteBuf
,介绍了各种非池化字节缓冲的分配、使用和回收。但在实践中一般不是直接调用他们的构造器来创建一个字节缓存的,而是通过分配器来分配,比如:

public static void main(String[] args) throws Exception {

	// 获取非池化ByteBuf分配器
	ByteBufAllocator alloc = UnpooledByteBufAllocator.DEFAULT;

	// 分配一个io buffer
	ByteBuf ioBuf = alloc.ioBuffer(128);

	// 分配一个堆内的字节缓冲
	ByteBuf heapBuf = alloc.heapBuffer(128);

	// 分配一个直接内存缓冲ByteBuf
	ByteBuf directBuf = alloc.directBuffer(128);
}	

这么做的好处,是将字节缓存的分配和应用层进行最大程度的解耦。比如alloc.ioBuffer(int)是一个特定于IO操作场景的字节缓冲分配方法,在这个场景中到底是direct缓冲好还是heap存缓冲好,留给Allocator去决定,而不应该在IO操作场景中直接指定要direct还是heap缓冲。
又比如alloc.directBuffer(int),从上一篇文章我们知道,直接内存缓冲也有好几个版本,有带Cleaner版本的,有不带Cleaner版本的,还有Unsafe版本的。具体用哪个版本,交给allocator根据当前运行环境(比如jdk版本、操作系统版本等)决定创建哪个版本的direct字节缓冲。

2. 整体逻辑

接下来来看看UnpooledByteBufAllocator的这几个方法是怎么分配ByteBuf的,首先上类的继承关系图:

图1. UnpooledByteBufAllocator的继承关系
(三) netty非池化内存的分配与回收:UnpooledByteBufAllocator_第1张图片
ByteBufAllocator定义了公用接口;而字节缓冲分配的主体逻辑,都是在抽象类AbstractByteBufAllocator中;两个非池化的和池化的分配器,真正完成了字节缓冲的分配。

比如ByteBuf ioBuffer(int initialCapacity),它在AbstractByteBufAllocator中是这样的:

    public ByteBuf ioBuffer(int initialCapacity) {
        if (PlatformDependent.hasUnsafe()) {
            return directBuffer(initialCapacity);
        }
        return heapBuffer(initialCapacity);
    }

根据平台是否支持Unsafe操作,判断是用堆内存还是直接内存。如果是使用堆内存,会调用到这里:

    public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
        if (initialCapacity == 0 && maxCapacity == 0) {
            return emptyBuf;
        }
        validate(initialCapacity, maxCapacity);
        return newHeapBuffer(initialCapacity, maxCapacity);
    }

做简单的校验之后,调用newHeapBuffer(int, int)真正分配了堆内字节缓冲,newHeapBuffer是个抽象方法,在后面讲UnpooledByteBufAllocator时具体讲解。

而如果决定用直接内存,则会调用到:

    public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
        if (initialCapacity == 0 && maxCapacity == 0) {
            return emptyBuf;
        }
        validate(initialCapacity, maxCapacity);
        return newDirectBuffer(initialCapacity, maxCapacity);
    }

进行简单的调用后,调用newDirectBuffer(int, int)真正分配了直接内存上的字节缓冲,newDirectBuffer也是个抽象方法,接下来看其在UnpooledByteBufAllocator中如何实现。

3. UnpooledByteBufAllocator

考察newDirectBuffer(int, int)是如何分配直接内存的,逻辑比较简单直接,做一些运行环境和设置相关的判断之后,直接调用构造器创建直接内存缓冲:

    protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
        final ByteBuf buf;
        if (PlatformDependent.hasUnsafe()) {
            buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) :
                    new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
        } else {
            buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
        }
        return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
    }

解释:

  1. PlatformDependent.hasUnsafe(),是否能使用sun.misc.Unsafe方式来访问内存,比如设置字节、读取字节等。有以下几个情况不能使用(1)如果当前是安卓操作系统;(2)应用设置了系统属性io.netty.noUnsafe=true;(3)当前JVM运行时环境没有sun.misc.Unsafe类,或者通过反正获取Unsafe类的一些属性或方法发生错误时。
  2. 创建出来的几个直接内存缓冲类对象,都继承自上一篇文章中分析的ByteBuf类:(二) netty非池化内存的表示:UnpooledHeapByteBuf和UnpooledDirectByteBuf,并扩充了Metric功能,即分配新的字节缓冲时,增加内存使用量计数器;释放字节缓冲时,减少计数器。如下代码,分配时:
    protected ByteBuffer allocateDirect(int initialCapacity) {
        ByteBuffer buffer = super.allocateDirect(initialCapacity);
        ((UnpooledByteBufAllocator) alloc()).incrementDirect(buffer.capacity());
        return buffer;
    }

    void incrementDirect(int amount) {
        metric.directCounter.add(amount);
    }

释放时:

    protected void freeDirect(ByteBuffer buffer) {
        int capacity = buffer.capacity();
        super.freeDirect(buffer);
        ((UnpooledByteBufAllocator) alloc()).decrementDirect(capacity);
    }

    void decrementDirect(int amount) {
        metric.directCounter.add(-amount);
    }

最后,考察剩下的ByteBuf newHeapBuffer(int, int)方法,逻辑和newDirectBuffer非常类似也非常简单,不多解释,上代码:

    protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
        return PlatformDependent.hasUnsafe() ?
                new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
                new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
    }

这两个Instrumented堆内字节缓冲,和直接内存直接缓冲一样,在分配和释放时会增减内存使用量计数器,不过堆内存使用了一个独立于直接内存的计数器:heapCounter。

4. 内存增长策略

当ByteBuf内存大小不够用、需扩充时,可以调用int calculateNewCapacity(int minNewCapacity, int maxCapacity)方法获取到应该扩充的大小,而不是自己胡乱指定大小,这也算是使用netty ByteBuf时的一个最佳实践。(当然你非常了解自己应用的内存使用规律时,完全可以自己决定ByteBuf容量如何增长。)简单说明下,minNewCapacity是应用所需要的最小容量,calculateNewCapacity返回的是字节缓冲真正的大小。
calculateNewCapacity的基本策略是:

  1. 如果应用需要的minNewCapacity刚好等于4M,则返回4M。
  2. 如果所需大于4M,则在所需容量的基础上加上4M,并返回,即大于4M则每次增长4M。
  3. 如果小于4M,则将64bytes不断翻倍,直到得到一个>minNewCapacity的值,返回。

你可能感兴趣的:(netty,netty,ByteBuf,内存分配器)