文章

6. ByteBuf原理

6. ByteBuf原理

ByteBuf定义

Buffer的存在是为了提高IO 的效率,当Buffer达到一定长度后再2进行数据传输操作,而不是每次写操作完后立即开始数据传输。

首先看看ByteBuf注释中的示意图

1
2
3
4
5
6
+-------------------+------------------+------------------+
| discardable bytes |  readable bytes  |  writable bytes  |
|                   |     (CONTENT)    |                  |
+-------------------+------------------+------------------+
|                   |                  |                  |
0      <=      readerIndex   <=   writerIndex    <=    capacity

ByteBuf提供了两个指针来支持顺序读取和写入操作: readerIndex和writerIndex。

同时,ByteBuf也定义了大量对ByteBuf进行操作的抽象方法以供子类实现。

AbstracByteBuf作为其主要的子类,实现了大部分方法。首先看看定义的变量和构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 读指针
int readerIndex;
// 写指针
int writerIndex;
// 保存读指针
private int markedReaderIndex;
// 保存写指针
private int markedWriterIndex;
// 最大分配容量
private int maxCapacity;
protected AbstractByteBuf(int maxCapacity) {
    checkPositiveOrZero(maxCapacity, "maxCapacity");
    this.maxCapacity = maxCapacity;
}

没有重写的方法则交给子类实现。再来看写操作的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public ByteBuf writeBytes(ByteBuf src) {
    writeBytes(src, src.readableBytes());
    return this;
}
public int readableBytes() {
    return writerIndex - readerIndex;
}
public ByteBuf writeBytes(ByteBuf src, int length) {
    // 参数验证
    if (checkBounds) {
        checkReadableBounds(src, length);
    }
    writeBytes(src, src.readerIndex(), length);
    src.readerIndex(src.readerIndex() + length);
    return this;
}
public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
    ensureWritable(length);
    setBytes(writerIndex, src, srcIndex, length);
    writerIndex += length;
    return this;
}

首先得到可读字节数,也就是写指针和读指针之间的长度。再调用自身的writeBytes方法,在保证写的长度小于可读长度的情况下再执行读操作。此处又掉了自身的writeBytes重载方法,首先对验证传入的length大于零,再对长度和容量的长度继续宁验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public ByteBuf ensureWritable(int minWritableBytes) {
    ensureWritable0(checkPositiveOrZero(minWritableBytes, "minWritableBytes"));
    return this;
}

final void ensureWritable0(int minWritableBytes) {
    // 获取当前写入索引
    final int writerIndex = writerIndex();
    // 计算目标容量,即当前写入索引加上需要的最小可写字节数
    final int targetCapacity = writerIndex + minWritableBytes;

    // 使用非短路的 & 运算符来减少分支 - 这是一个热路径,目标容量应该很少溢出
    // 如果目标容量大于等于0且小于等于容量,则无需扩容
    if (targetCapacity >= 0 & targetCapacity <= capacity()) {
        // 确保缓冲区可访问
        // 引用计数为0时,抛出IllegalReferenceCountException异常
        ensureAccessible();
        return;
    }

    // 检查目标容量是否超出边界
    // 如果目标容量小于0或大于最大容量,则抛出索引越界异常
    if (checkBounds && (targetCapacity < 0 || targetCapacity > maxCapacity)) {
        ensureAccessible();
        // 抛出索引越界异常
        throw new IndexOutOfBoundsException(String.format(
            "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
            writerIndex, minWritableBytes, maxCapacity, this));
    }

    // 获得可以写入的字节数
    final int fastWritable = maxFastWritableBytes();
    // 计算新的容量,如果fastWritable大于等于所需的最小可写字节数,则使用当前写入索引加上快速可写字节数
    // 否则,使用分配器计算新的容量
    int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
        : alloc().calculateNewCapacity(targetCapacity, maxCapacity);

    // 调整到新的容量
    capacity(newCapacity);
}

做了两个参数校验:

  • ByteBuf写入的长度如果要小于可写字节数,直接进行写操作,无需扩容
  • minWritableBytes+ writerIndex> maxCapacity 也就是需要写入的长度+写指针必须要小于最大分配的内存, 否则报错

如果需要写入的长度超过了可写字节数, 并且需要写入的长度+写指针不超过最大内存, 则就开始了ByteBuf的自动扩容。其中alloc()返回的是当前bytebuf返回的缓冲区分配器对象。接着看扩容的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
    // 检查最小新容量是否为正数
    checkPositiveOrZero(minNewCapacity, "minNewCapacity");

    // 如果最小新容量大于最大容量,抛出异常
    if (minNewCapacity > maxCapacity) {
        throw new IllegalArgumentException(String.format(
            "minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
            minNewCapacity, maxCapacity));
    }

    // 定义阈值,表示4 MiB的页面大小
    final int threshold = CALCULATE_THRESHOLD; // 4 MiB page

    // 如果最小新容量等于阈值,直接返回阈值
    if (minNewCapacity == threshold) {
        return threshold;
    }

    // 如果最小新容量超过阈值,则不进行翻倍,而是增加阈值
    if (minNewCapacity > threshold) {
        int newCapacity = minNewCapacity / threshold * threshold; // 计算新的容量
        // 如果新的容量超过最大容量减去阈值,则将新的容量设为最大容量
        if (newCapacity > maxCapacity - threshold) {
            newCapacity = maxCapacity;
        } else {
            newCapacity += threshold; // 否则增加阈值
        }
        return newCapacity; // 返回计算后的新容量
    }

    // 如果最小新容量在64到阈值之间,找到下一个正的2的幂
    final int newCapacity = MathUtil.findNextPositivePowerOfTwo(Math.max(minNewCapacity, 64));
    // 返回新的容量,确保不超过最大容量
    return Math.min(newCapacity, maxCapacity);
}

也就是如果小于阈值(4mb), 采用倍增的方式,如果大于阈值(4mb),采用平移4mb的方式。

再回到writeBytes(ByteBuf src, int srcIndex, int length)方法:

1
2
3
4
5
6
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
    ensureWritable(length);
    setBytes(writerIndex, src, srcIndex, length);
    writerIndex += length;
    return this;
}

接下来调用setBytes从当前byteBuf的writerIndex节点开始写入, 将源缓冲区src的读指针位置, 写lenght个字节。AbstractByteBuf并没有提供具体实现,最后交给不同的子类实现。写完后移动写指针。

ByteBuf分类

Pooled和Unpooled

  • pooled是从一块内存里去取一段连续内存封装成byteBuf。
  • Unpooled是分配的时候直接调用系统api进行实现。

Direct和Heap

  • 基于直接内存的ByteBuf, 具体标志是类名中包含单词Direct的ByteBuf。
  • 基于堆内存的ByteBuf, 具体标志是类名中包含单词heap的ByteBuf。

Safe和Unsafe

  • Unsafe类型的byteBuf, 则可以直接拿到byteBuf在jvm中的具体内存, 可以通过调用jdk的Usafe对象进行读写
  • 非Unsafe不能拿到jvm的具体内存

在实际debug中能够发现实际上使用的时PooledUnsafeDirectByteBuf (windows 11),以下以Pooled来介绍HeapBuf和DirectBuf

首先看PooledHeapByteBuf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class PooledHeapByteBuf extends PooledByteBuf<byte[]> {

    private static final ObjectPool<PooledHeapByteBuf> RECYCLER = ObjectPool.newPool(
            new ObjectCreator<PooledHeapByteBuf>() {
        @Override
        public PooledHeapByteBuf newObject(Handle<PooledHeapByteBuf> handle) {
            return new PooledHeapByteBuf(handle, 0);
        }
    });

    static PooledHeapByteBuf newInstance(int maxCapacity) {
        PooledHeapByteBuf buf = RECYCLER.get();
        buf.reuse(maxCapacity);
        return buf;
    }

    PooledHeapByteBuf(Handle<? extends PooledHeapByteBuf> recyclerHandle, int maxCapacity) {
        super(recyclerHandle, maxCapacity);
    }
	// 省略代码
}

首先注意到存在一个类型为ObjectPool的类变量RECYCLER,根据注释发现这是一个对象池,用于复用Buf,当对象池中存在可以复用的对象即复用该对象,否则重新创建对象,初始化时也定义了创建对象的方法。并且还存在一个static方法newInstance用于获取对象,即从对象池中获取对象,重新初始化后返回,这也解释了为什么构造器的修饰符为defaul。

再来看构造器的执行流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PooledHeapByteBuf(Handle<? extends PooledHeapByteBuf> recyclerHandle, int maxCapacity) {
    super(recyclerHandle, maxCapacity);
}
protected PooledByteBuf(Handle<? extends PooledByteBuf<T>> recyclerHandle, int maxCapacity) {
    super(maxCapacity);
    this.recyclerHandle = (EnhancedHandle<PooledByteBuf<T>>) recyclerHandle;
}
protected AbstractReferenceCountedByteBuf(int maxCapacity) {
    super(maxCapacity);
    updater.setInitialValue(this);
}
protected AbstractByteBuf(int maxCapacity) {
    checkPositiveOrZero(maxCapacity, "maxCapacity");
    this.maxCapacity = maxCapacity;
}

首先调用PooledByteBuf的构造器来初始化recyclerHandle:当引用数为0是释放资源。

AbstractReferenceCountedByteBuf构造器中初始化了updater,也就是Buf的引用计数器。

最后到AbstractByteBuf中初始化最大容量。

创建完成后使用setBytes方法初始化PooledByteBuf的成员变量T memory。根据PooledHeapByteBuf得知Buf最后的数据结构为byte[]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {

    private final EnhancedHandle<PooledByteBuf<T>> recyclerHandle;

    protected PoolChunk<T> chunk;
    protected long handle;
    protected T memory;
    protected int offset;
    protected int length;
    int maxLength;
    PoolThreadCache cache;
    ByteBuffer tmpNioBuf;
    private ByteBufAllocator allocator;
}

class PooledHeapByteBuf extends PooledByteBuf<byte[]>{
	// 省略代码
}
1
2
3
4
5
6
7
8
9
10
11
public final ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
    checkSrcIndex(index, length, srcIndex, src.capacity());
    if (src.hasMemoryAddress()) {
        PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, memory, idx(index), length);
    } else if (src.hasArray()) {
        setBytes(index, src.array(), src.arrayOffset() + srcIndex, length);
    } else {
        src.getBytes(srcIndex, memory, idx(index), length);
    }
    return this;
}

接着看PooledDirectByteBuf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
final class PooledDirectByteBuf extends PooledByteBuf<ByteBuffer> {

    private static final ObjectPool<PooledDirectByteBuf> RECYCLER = ObjectPool.newPool(
            new ObjectCreator<PooledDirectByteBuf>() {
        @Override
        public PooledDirectByteBuf newObject(Handle<PooledDirectByteBuf> handle) {
            return new PooledDirectByteBuf(handle, 0);
        }
    });

    static PooledDirectByteBuf newInstance(int maxCapacity) {
        PooledDirectByteBuf buf = RECYCLER.get();
        buf.reuse(maxCapacity);
        return buf;
    }

    private PooledDirectByteBuf(Handle<PooledDirectByteBuf> recyclerHandle, int maxCapacity) {
        super(recyclerHandle, maxCapacity);
    }
 	// 省略代码   
}

与PooledHeapByteBuf一样,通过newInstance创建对象。但二者的访问修饰符有所区别,PooledHeapByteBuf不为private的原因为PooledUnsafeHeapByteBuf继承了PooledHeapByteBuf。

目前为止没有看到Buf数据的初始化,接着往下走发现存在init方法来初始化成员变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void init(PoolChunk<ByteBuffer> chunk, ByteBuffer nioBuffer,
          long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
    super.init(chunk, nioBuffer, handle, offset, length, maxLength, cache);
    initMemoryAddress();
}
PooledByteBuf#init
    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;
    assert !PoolChunk.isSubpage(handle) ||
        chunk.arena.sizeClass.size2SizeIdx(maxLength) <= chunk.arena.sizeClass.smallMaxSizeIdx:
    "Allocated small sub-page handle for a buffer size that isn't \"small.\"";

    chunk.incrementPinnedMemory(maxLength);
    this.chunk = chunk;
    memory = chunk.memory;
    tmpNioBuf = nioBuffer;
    allocator = chunk.arena.parent;
    this.cache = cache;
    this.handle = handle;
    this.offset = offset;
    this.length = length;
    this.maxLength = maxLength;
}

初始化后调用再初始化Buf的内存地址

1
2
3
private void initMemoryAddress() {
    memoryAddress = PlatformDependent.directBufferAddress(memory) + offset;
}

ByteBufAllocator

在以上两个类的初始化中都传入了ByteBufAllocator,顾名思义就是分配缓冲区的工具,抽象类AbstractByteBufAllocator实现了ByteBufAllocator接口, 并且实现了其大部分功能。先来看看分配buffer的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public ByteBuf buffer() {
    if (directByDefault) {
        return directBuffer();
    }
    return heapBuffer();
}

/**
* Instance use heap buffers by default
*/
protected AbstractByteBufAllocator() {
    this(false);
}

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

根据成员变量directByDefault来判断创建那种Buf。从构造方法可以发现,默认为创建HeapBuf,需要创建的DirectBuf时还需要系统支持Unsafe。

先来看directBuf的创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
public ByteBuf directBuffer() {
    return directBuffer(DEFAULT_INITIAL_CAPACITY, DEFAULT_MAX_CAPACITY);
}

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

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

调用了重载的directBuffer方法,传入了默认初始容量和默认最大容量,分别为256和Integer. MAX_VALUE,具体的创建交给子类实现。

heapBuf与directBuf类似,不再赘述。

newDirectBuffer和newHeapBuffer两个抽象方法中, 在其子类PooledByteBufAllocator和UnpooledByteBufAllocator中都有实现

以PooledByteBufAllocator为例,先来看newHeapBuffer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
    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);
}
1
PoolThreadCache cache = threadCache.get();

首先获取当前线程的缓存。这缓存对象为PooledByteBufAllocator的内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private final class PoolThreadLocalCache extends FastThreadLocal<PoolThreadCache> {
    private final boolean useCacheForAllThreads;

    PoolThreadLocalCache(boolean useCacheForAllThreads) {
        this.useCacheForAllThreads = useCacheForAllThreads;
    }

    @Override
    protected synchronized PoolThreadCache initialValue() {
        final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas);
        final PoolArena<ByteBuffer> directArena = leastUsedArena(directArenas);

        final Thread current = Thread.currentThread();
        final EventExecutor executor = ThreadExecutorMap.currentExecutor();

        if (useCacheForAllThreads ||
            // If the current thread is a FastThreadLocalThread we will always use the cache
            current instanceof FastThreadLocalThread ||
            // The Thread is used by an EventExecutor, let's use the cache as the chances are good that we
            // will allocate a lot!
            executor != null) {
            final PoolThreadCache cache = new PoolThreadCache(
                heapArena, directArena, smallCacheSize, normalCacheSize,
                DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL, useCacheFinalizers(current));

            if (DEFAULT_CACHE_TRIM_INTERVAL_MILLIS > 0) {
                if (executor != null) {
                    executor.scheduleAtFixedRate(trimTask, DEFAULT_CACHE_TRIM_INTERVAL_MILLIS,
                                                 DEFAULT_CACHE_TRIM_INTERVAL_MILLIS, TimeUnit.MILLISECONDS);
                }
            }
            return cache;
        }
        // No caching so just use 0 as sizes.
        return new PoolThreadCache(heapArena, directArena, 0, 0, 0, 0, false);
    }
}

该类继承了一个与ThreadLocal类似的缓存FastThreadLocal。

在重写的initialValue方法中初始化了heapArena和directArena两个属性之后, 通过new PoolThreadCache()这种方式创建了PoolThreadCache对象。

PoolThreadLocalCache是一个FastThreadLocal, 而PoolThreadCache才是线程局部缓存, 这两个类名非常非常像, 千万别搞混了

其中heapArena和directArena是分别是用来分配堆和堆外内存用的两个对象, 以directArena为例, 通过leastUsedArena(directArenas)这种方式获得, directArenas是一个directArena类型的数组, leastUsedArena(directArenas)这个方法是用来获取数组中一个使用最少的directArena对象

directArenas是PooledByteBufAllocator的成员变量, 是在其构造方法中初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
                              int smallCacheSize, int normalCacheSize,
                              boolean useCacheForAllThreads, int directMemoryCacheAlignment) {
	// 省略代码
    if (nHeapArena > 0) {
        heapArenas = newArenaArray(nHeapArena);
        List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
        final SizeClasses sizeClasses = new SizeClasses(pageSize, pageShifts, chunkSize, 0);
        for (int i = 0; i < heapArenas.length; i ++) {
            PoolArena.HeapArena arena = new PoolArena.HeapArena(this, sizeClasses);
            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);
        final SizeClasses sizeClasses = new SizeClasses(pageSize, pageShifts, chunkSize,
                directMemoryCacheAlignment);
        for (int i = 0; i < directArenas.length; i ++) {
            PoolArena.DirectArena arena = new PoolArena.DirectArena(this, sizeClasses);
            directArenas[i] = arena;
            metrics.add(arena);
        }
        directArenaMetrics = Collections.unmodifiableList(metrics);
    } else {
        directArenas = null;
        directArenaMetrics = Collections.emptyList();
    }
    metric = new PooledByteBufAllocatorMetric(this);
}

可以的看到heapArenas通过heapArenas = newArenaArray(nHeapArena)初始化。初始值为

1
2
3
4
5
6
7
8
9
10
11
12
DEFAULT_NUM_HEAP_ARENA = Math.max(0,
        SystemPropertyUtil.getInt(
                "io.netty.allocator.numHeapArenas",
                (int) Math.min(
                        defaultMinNumArena,
                        runtime.maxMemory() / defaultChunkSize / 2 / 3)));
DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
        SystemPropertyUtil.getInt(
                "io.netty.allocator.numDirectArenas",
                (int) Math.min(
                        defaultMinNumArena,
                        PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));

看看newArenaArray

1
2
3
private static <T> PoolArena<T>[] newArenaArray(int size) {
    return new PoolArena[size];
}

这里仅创建了PoolArena数组。创建完成后在循环中为数组赋值。

再回到newDirectBuffer方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    // 从当前线程的缓存中获取
    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构造方法中将directArena和heapArena中保存在成员变量中, 这样就可以直接通过cache.directArena这种方式拿到其成员变量的内容。再根据是否为null开始创建Buf。

到这里大概明白了arena的作用,Allocator创建和线程数量相等的arena, 并以数组的形式存储在PooledByteBufAllocator的成员变量中, 每一个PoolThreadCache创建的时候, 都会在当前线程拿到一个arena, 并保存在自身的成员变量中。

PoolThreadCache除了维护了arena之外, 还维护了一个缓存列表, 我们在重复分配ByteBuf的时候, 并不需要每次都通过arena进行分配, 可以直接从缓存列表中拿一个ByteBuf。

在PooledByteBufAllocator中维护了2个值:

  • smallCacheSize
  • normalCacheSize

代表能保存多少个不同类型的缓存。注意这里抛弃了tinyCache,融入了smallCache。

回到PoolThreadLocalCache的构造方法能够发现这些值通过PoolThreadCache的构造方法传入。

1
2
3
4
5
6
7
8
9
10
11
private static final int DEFAULT_SMALL_CACHE_SIZE;
private static final int DEFAULT_NORMAL_CACHE_SIZE;

static {
    DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.smallCacheSize", 256);
    DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.normalCacheSize", 64);
}

public PooledByteBufAllocator(boolean preferDirect) {
    this(preferDirect, DEFAULT_NUM_HEAP_ARENA, DEFAULT_NUM_DIRECT_ARENA, DEFAULT_PAGE_SIZE, DEFAULT_MAX_ORDER);
}

传入了静态类变量,分别初始化为256,64。在重载的构造方法中初始化缓存大小。

1
2
3
4
5
6
7
8
9
public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
                              int smallCacheSize, int normalCacheSize,
                              boolean useCacheForAllThreads, int directMemoryCacheAlignment) {
    super(preferDirect);
    threadCache = new PoolThreadLocalCache(useCacheForAllThreads);
    this.smallCacheSize = smallCacheSize;
    this.normalCacheSize = normalCacheSize;
    // 省略代码
}

这也就是PoolThreadLocalCache initialValue所使用的参数。

回到PoolThreadCache的创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,
                int smallCacheSize, int normalCacheSize, int maxCachedBufferCapacity,
                int freeSweepAllocationThreshold, boolean useFinalizer) {
    checkPositiveOrZero(maxCachedBufferCapacity, "maxCachedBufferCapacity");
    this.freeSweepAllocationThreshold = freeSweepAllocationThreshold;
    this.heapArena = heapArena;
    this.directArena = directArena;
    if (directArena != null) {
        smallSubPageDirectCaches = createSubPageCaches(smallCacheSize, directArena.sizeClass.nSubpages);
        normalDirectCaches = createNormalCaches(normalCacheSize, maxCachedBufferCapacity, directArena);
        directArena.numThreadCaches.getAndIncrement();
    } else {
        // No directArea is configured so just null out all caches
        smallSubPageDirectCaches = null;
        normalDirectCaches = null;
    }
    if (heapArena != null) {
        // Create the caches for the heap allocations
        smallSubPageHeapCaches = createSubPageCaches(smallCacheSize, heapArena.sizeClass.nSubpages);
        normalHeapCaches = createNormalCaches(normalCacheSize, maxCachedBufferCapacity, heapArena);
        heapArena.numThreadCaches.getAndIncrement();
    } else {
        // No heapArea is configured so just null out all caches
        smallSubPageHeapCaches = null;
        normalHeapCaches = null;
    }

    // Only check if there are caches in use.
    if ((smallSubPageDirectCaches != null || normalDirectCaches != null
         || smallSubPageHeapCaches != null || normalHeapCaches != null)
        && freeSweepAllocationThreshold < 1) {
        throw new IllegalArgumentException("freeSweepAllocationThreshold: "
                                           + freeSweepAllocationThreshold + " (expected: > 0)");
    }
    freeOnFinalize = useFinalizer ? new FreeOnFinalize(this) : null;
}

现在关注smallCache和normalCache的初始化,也就对应着PoolThreadCache中的成员变量

1
2
3
4
5
// Hold the caches for the different size classes, which are small and normal.
private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
private final MemoryRegionCache<byte[]>[] normalHeapCaches;
private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;

首先看smallSubPageDirectCaches = createSubPageCaches(smallCacheSize, directArena.sizeClass.nSubpages);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static <T> MemoryRegionCache<T>[] createSubPageCaches(
    int cacheSize, int numCaches) {
    if (cacheSize > 0 && numCaches > 0) {
        @SuppressWarnings("unchecked")
        MemoryRegionCache<T>[] cache = new MemoryRegionCache[numCaches];
        for (int i = 0; i < cache.length; i++) {
            // TODO: maybe use cacheSize / cache.length
            cache[i] = new SubPageMemoryRegionCache<T>(cacheSize);
        }
        return cache;
    } else {
        return null;
    }
}

private abstract static class MemoryRegionCache<T> {
    private final int size;
    private final Queue<Entry<T>> queue;
    private final SizeClass sizeClass;
    private int allocations;

    MemoryRegionCache(int size, SizeClass sizeClass) {
        this.size = MathUtil.safeFindNextPositivePowerOfTwo(size);
        queue = PlatformDependent.newFixedMpscQueue(this.size);
        this.sizeClass = sizeClass;
    }
    // 省略代码
}

从代码中可以发现:创建了numCaches个大小为cacheSize的SubPageMemoryRegionCache。这个类继承于MemoryRegionCache,其中使用Queue来保存缓存。

接着看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static <T> MemoryRegionCache<T>[] createNormalCaches(
        int cacheSize, int maxCachedBufferCapacity, PoolArena<T> area) {
    if (cacheSize > 0 && maxCachedBufferCapacity > 0) {
        int max = Math.min(area.sizeClass.chunkSize, maxCachedBufferCapacity);
        // Create as many normal caches as we support based on how many sizeIdx we have and what the upper
        // bound is that we want to cache in general.
        List<MemoryRegionCache<T>> cache = new ArrayList<MemoryRegionCache<T>>() ;
        for (int idx = area.sizeClass.nSubpages; idx < area.sizeClass.nSizes &&
                area.sizeClass.sizeIdx2size(idx) <= max; idx++) {
            cache.add(new NormalMemoryRegionCache<T>(cacheSize));
        }
        return cache.toArray(new MemoryRegionCache[0]);
    } else {
        return null;
    }
}

与smallCache的情形类似,只不过用于保存缓存的不是数组而是Arraylist。

再来看NormalMemoryRegionCache和SubPageMemoryRegionCache的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Cache used for buffers which are backed by TINY or SMALL size.
 */
private static final class SubPageMemoryRegionCache<T> extends MemoryRegionCache<T> {
    SubPageMemoryRegionCache(int size) {
        super(size, SizeClass.Small);
    }

    @Override
    protected void initBuf(
            PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, PooledByteBuf<T> buf, int reqCapacity,
            PoolThreadCache threadCache) {
        chunk.initBufWithSubpage(buf, nioBuffer, handle, reqCapacity, threadCache);
    }
}

/**
 * Cache used for buffers which are backed by NORMAL size.
 */
private static final class NormalMemoryRegionCache<T> extends MemoryRegionCache<T> {
    NormalMemoryRegionCache(int size) {
        super(size, SizeClass.Normal);
    }

    @Override
    protected void initBuf(
            PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, PooledByteBuf<T> buf, int reqCapacity,
            PoolThreadCache threadCache) {
        chunk.initBuf(buf, nioBuffer, handle, reqCapacity, threadCache);
    }
}

从类定义上来看二者并无区别,只是在创建时向父类传递的类型(SizeClass)存在区别。

// TODO:io.netty.buffer.PoolChunk#initBuf中二者的区别

directArena分配Buf

回到newDirectBuffer方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
    // 从当前线程的缓存中获取
    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);
}

当directArena不为空时,通过allocate方法分配了一个PoolArena<ByteBuffer>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
    PooledByteBuf<T> buf = newByteBuf(maxCapacity);
    allocate(cache, buf, reqCapacity);
    return buf;
}

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

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
    final int sizeIdx = sizeClass.size2SizeIdx(reqCapacity);

    if (sizeIdx <= sizeClass.smallMaxSizeIdx) {
        tcacheAllocateSmall(cache, buf, reqCapacity, sizeIdx);
    } else if (sizeIdx < sizeClass.nSizes) {
        tcacheAllocateNormal(cache, buf, reqCapacity, sizeIdx);
    } else {
        int normCapacity = sizeClass.directMemoryCacheAlignment > 0
            ? sizeClass.normalizeSize(reqCapacity) : reqCapacity;
        // Huge allocations are never served via the cache so just call allocateHuge
        allocateHuge(buf, normCapacity);
    }
}

首先通过newByteBuf获得Buf对象,在通过allocate进行分配。newByteBuf中通过newInstance静态方法获得Buf,这里的静态方法也就是前面提到的从对象池中获取对象。

在allocate方法中,首先计算出规范后的缓存容量,再根据容量大小来决定分配那种缓存。如果small和normal均不满足,则申请huge缓存。

以tcacheAllocateSmall为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void tcacheAllocateSmall(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity,
                                 final int sizeIdx) {

    // 尝试从缓存中分配小型内存块
    if (cache.allocateSmall(this, buf, reqCapacity, sizeIdx)) {
        // 如果成功从缓存中分配内存,则直接返回
        return;
    }

    /*
         * 在头部上进行同步。这是必要的,因为 {@link PoolChunk#allocateSubpage(int)} 和
         * {@link PoolChunk#free(long)} 可能会修改双向链表。
         */
    final PoolSubpage<T> head = smallSubpagePools[sizeIdx]; // 获取对应大小索引的头部
    final boolean needsNormalAllocation; // 标记是否需要正常分配
    head.lock(); // 锁定头部以进行安全操作
    try {
        final PoolSubpage<T> s = head.next; // 获取下一个子页面
        needsNormalAllocation = s == head; // 检查是否需要正常分配
        if (!needsNormalAllocation) {
            // 确保子页面未被销毁且元素大小与索引匹配
            assert s.doNotDestroy && s.elemSize == sizeClass.sizeIdx2size(sizeIdx) : "doNotDestroy=" +
                s.doNotDestroy + ", elemSize=" + s.elemSize + ", sizeIdx=" + sizeIdx;
            long handle = s.allocate(); // 从子页面分配内存
            assert handle >= 0; // 确保分配成功
            s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache); // 初始化缓冲区
        }
    } finally {
        head.unlock(); // 解锁头部
    }

    // 如果需要正常分配,则进行正常分配
    if (needsNormalAllocation) {
        lock(); // 锁定以进行安全操作
        try {
            allocateNormal(buf, reqCapacity, sizeIdx, cache); // 调用正常分配方法
        } finally {
            unlock(); // 解锁
        }
    }

    incSmallAllocation(); // 增加小型分配计数
}

首先从缓存中分配内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
boolean allocateSmall(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int sizeIdx) {
    return allocate(cacheForSmall(area, sizeIdx), buf, reqCapacity);
}

private MemoryRegionCache<?> cacheForSmall(PoolArena<?> area, int sizeIdx) {
    if (area.isDirect()) {
        return cache(smallSubPageDirectCaches, sizeIdx);
    }
    return cache(smallSubPageHeapCaches, sizeIdx);
}

private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int sizeIdx) {
    if (cache == null || sizeIdx > cache.length - 1) {
        return null;
    }
    return cache[sizeIdx];
}

通过cacheForSmall方法来获取位于sizeIdx的缓存,如果规范化后得到的sizeIdx在缓存的范围内的话,返回cache[sizeIdx]。

随后进入allocate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) {
    if (cache == null) {
        // no cache found so just return false here
        return false;
    }
    boolean allocated = cache.allocate(buf, reqCapacity, this);
    if (++ allocations >= freeSweepAllocationThreshold) {
        allocations = 0;
        trim();
    }
    return allocated;
}

public final boolean allocate(PooledByteBuf<T> buf, int reqCapacity, PoolThreadCache threadCache) {
    Entry<T> entry = queue.poll();
    if (entry == null) {
        return false;
    }
    initBuf(entry.chunk, entry.nioBuffer, entry.handle, buf, reqCapacity, threadCache);
    entry.unguardedRecycle();

    // allocations is not thread-safe which is fine as this is only called from the same thread all time.
    ++ allocations;
    return true;
}

如果cache不为空,调用cache的allocate方法继续分配。这里会尝试从io.netty.buffer.PoolThreadCache.MemoryRegionCache#queue来获取已分配的缓存,如果queue不为空,获得entry后开始初始化;为空则返回false等到后续在分配。

回到PoolThreadCache#allocate

1
2
3
4
5
6
7
8
9
10
11
12
private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) {
    if (cache == null) {
        // no cache found so just return false here
        return false;
    }
    boolean allocated = cache.allocate(buf, reqCapacity, this);
    if (++ allocations >= freeSweepAllocationThreshold) {
        allocations = 0;
        trim();
    }
    return allocated;
}

之后再判断已分配的容量是否到了GC的阈值,是的话开始释放资源,在此不做展开。

1
2
3
4
5
6
void trim() {
    trim(smallSubPageDirectCaches);
    trim(normalDirectCaches);
    trim(smallSubPageHeapCaches);
    trim(normalHeapCaches);
}

再回到io.netty.buffer.PoolArena#tcacheAllocateSmall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void tcacheAllocateSmall(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity,
                                 final int sizeIdx) {

    // 尝试从缓存中分配小型内存块
    if (cache.allocateSmall(this, buf, reqCapacity, sizeIdx)) {
        // 如果成功从缓存中分配内存,则直接返回
        return;
    }

    /*
         * 在头部上进行同步。这是必要的,因为 {@link PoolChunk#allocateSubpage(int)} 和
         * {@link PoolChunk#free(long)} 可能会修改双向链表。
         */
    final PoolSubpage<T> head = smallSubpagePools[sizeIdx]; // 获取对应大小索引的头部
    final boolean needsNormalAllocation; // 标记是否需要正常分配
    head.lock(); // 锁定头部以进行安全操作
    try {
        final PoolSubpage<T> s = head.next; // 获取下一个子页面
        needsNormalAllocation = s == head; // 检查是否需要正常分配
        if (!needsNormalAllocation) {
            // 确保子页面未被销毁且元素大小与索引匹配
            assert s.doNotDestroy && s.elemSize == sizeClass.sizeIdx2size(sizeIdx) : "doNotDestroy=" +
                s.doNotDestroy + ", elemSize=" + s.elemSize + ", sizeIdx=" + sizeIdx;
            long handle = s.allocate(); // 从子页面分配内存
            assert handle >= 0; // 确保分配成功
            s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache); // 初始化缓冲区
        }
    } finally {
        head.unlock(); // 解锁头部
    }

    // 如果需要正常分配,则进行正常分配
    if (needsNormalAllocation) {
        lock(); // 锁定以进行安全操作
        try {
            allocateNormal(buf, reqCapacity, sizeIdx, cache); // 调用正常分配方法
        } finally {
            unlock(); // 解锁
        }
    }

    incSmallAllocation(); // 增加小型分配计数
}

在根据参数判断是否使用subpage缓存分配。此处先谈论normal的方式。

Normal 缓存

在进入allocateNormal方法前,对成员对象ReentrantLock lock进行加锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache threadCache) {
        // 确保当前线程持有锁
        assert lock.isHeldByCurrentThread();
        
        // 尝试从不同的内存池中分配内存
        // 如果任一分配成功,则直接返回
        if (q050.allocate(buf, reqCapacity, sizeIdx, threadCache) ||
            q025.allocate(buf, reqCapacity, sizeIdx, threadCache) ||
            q000.allocate(buf, reqCapacity, sizeIdx, threadCache) ||
            qInit.allocate(buf, reqCapacity, sizeIdx, threadCache) ||
            q075.allocate(buf, reqCapacity, sizeIdx, threadCache)) {
            return; // 如果成功分配,退出方法
        }

        // 如果所有池都无法分配内存,则创建一个新的内存块
        PoolChunk<T> c = newChunk(sizeClass.pageSize, sizeClass.nPSizes, sizeClass.pageShifts, sizeClass.chunkSize);
        
        // 尝试在新创建的内存块中分配所需的内存
        boolean success = c.allocate(buf, reqCapacity, sizeIdx, threadCache);
        
        // 确保分配成功
        assert success;
        
        // 将新创建的内存块添加到初始化队列中
        qInit.add(c);
    }

首先尝试从内存块池(PoolChunkList)中分配缓存,失败的话创建新的内存块再进行分配。

先来看内存块池分配缓存的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache threadCache) {
    int normCapacity = arena.sizeClass.sizeIdx2size(sizeIdx);
    if (normCapacity > maxCapacity) {
        // Either this PoolChunkList is empty or the requested capacity is larger then the capacity which can
        // be handled by the PoolChunks that are contained in this PoolChunkList.
        return false;
    }

    for (PoolChunk<T> cur = head; cur != null; cur = cur.next) {
        if (cur.allocate(buf, reqCapacity, sizeIdx, threadCache)) {
            if (cur.freeBytes <= freeMinThreshold) {
                remove(cur);
                nextList.add(cur);
            }
            return true;
        }
    }
    return false;
}

boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int sizeIdx, PoolThreadCache cache) {
    final long handle; // 用于存储分配的句柄
    // 检查请求的大小索引是否在小型内存块的范围内
    if (sizeIdx <= arena.sizeClass.smallMaxSizeIdx) {
        final PoolSubpage<T> nextSub; // 用于存储下一个子页面
        // 小型内存块的分配
        // 获取 PoolArena 拥有的 PoolSubPage 池的头部并对其进行同步
        // 这是必要的,因为我们可能会将其添加回去,从而改变链表结构
        PoolSubpage<T> head = arena.smallSubpagePools[sizeIdx];
        head.lock(); // 锁定头部以确保线程安全
        try {
            nextSub = head.next; // 获取下一个子页面
            // 检查下一个子页面是否有效且未被销毁
            if (nextSub != head) {
                assert nextSub.doNotDestroy && nextSub.elemSize == arena.sizeClass.sizeIdx2size(sizeIdx) :
                "doNotDestroy=" + nextSub.doNotDestroy + ", elemSize=" + nextSub.elemSize + ", sizeIdx=" +
                    sizeIdx;
                handle = nextSub.allocate(); // 从下一个子页面分配内存
                assert handle >= 0; // 确保分配成功
                assert isSubpage(handle); // 确保句柄是子页面
                nextSub.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache); // 初始化缓冲区
                return true; // 返回成功
            }
            // 如果没有可用的子页面,则尝试分配新的子页面
            handle = allocateSubpage(sizeIdx, head);
            if (handle < 0) {
                return false; // 分配失败
            }
            assert isSubpage(handle); // 确保句柄是子页面
        } finally {
            head.unlock(); // 解锁头部
        }
    } else {
        // 正常内存块的分配
        // runSize 必须是 pageSize 的倍数
        int runSize = arena.sizeClass.sizeIdx2size(sizeIdx); // 获取请求大小对应的运行大小
        handle = allocateRun(runSize); // 分配运行
        if (handle < 0) {
            return false; // 分配失败
        }
        assert !isSubpage(handle); // 确保句柄不是子页面
    }

    // 从缓存中获取 NIO 缓冲区(如果存在)
    ByteBuffer nioBuffer = cachedNioBuffers != null ? cachedNioBuffers.pollLast() : null;
    // 初始化缓冲区
    initBuf(buf, nioBuffer, handle, reqCapacity, cache);
    return true; // 返回成功
}

遍历列表中的每个元素,尝试进行分配。由于此处是第一次进入,每个内存块池均为空,具体逻辑先跳过。

// TODO

内存块池分配失败后创建新的内存块。根据是否需要对齐进行内存分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected PoolChunk<ByteBuffer> newChunk(int pageSize, int maxPageIdx,
                                         int pageShifts, int chunkSize) {
    // 检查直接内存缓存对齐是否为 0
    if (sizeClass.directMemoryCacheAlignment == 0) {
        // 分配直接内存
        ByteBuffer memory = allocateDirect(chunkSize);
        // 返回新的 PoolChunk 实例
        return new PoolChunk<ByteBuffer>(this, memory, memory, pageSize, pageShifts,
                                         chunkSize, maxPageIdx);
    }

    // 如果需要对齐,分配额外的内存
    final ByteBuffer base = allocateDirect(chunkSize + sizeClass.directMemoryCacheAlignment);
    // 对齐直接内存缓冲区
    final ByteBuffer memory = PlatformDependent.alignDirectBuffer(base, sizeClass.directMemoryCacheAlignment);
    // 返回新的 PoolChunk 实例
    return new PoolChunk<ByteBuffer>(this, base, memory, pageSize,
                                     pageShifts, chunkSize, maxPageIdx);
}

private static ByteBuffer allocateDirect(int capacity) {
    return PlatformDependent.useDirectBufferNoCleaner() ? PlatformDependent.allocateDirectNoCleaner(capacity)
        : ByteBuffer.allocateDirect(capacity);
}

分配好内存块之后,在此内存块上分配缓存。分配后开始初始化initBuf(buf, nioBuffer, handle, reqCapacity, cache);,最后将内存块放入内存块池。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void initBuf(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity,
             PoolThreadCache threadCache) {
    if (isSubpage(handle)) {
        initBufWithSubpage(buf, nioBuffer, handle, reqCapacity, threadCache);
    } else {
        int maxLength = runSize(pageShifts, handle);
        buf.init(this, nioBuffer, handle, runOffset(handle) << pageShifts,
                 reqCapacity, maxLength, arena.parent.threadCache());
    }
}

void initBufWithSubpage(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity,
                        PoolThreadCache threadCache) {
    int runOffset = runOffset(handle);
    int bitmapIdx = bitmapIdx(handle);

    PoolSubpage<T> s = subpages[runOffset];
    assert s.isDoNotDestroy();
    assert reqCapacity <= s.elemSize : reqCapacity + "<=" + s.elemSize;

    int offset = (runOffset << pageShifts) + bitmapIdx * s.elemSize;
    buf.init(this, nioBuffer, handle, offset, reqCapacity, s.elemSize, threadCache);
}

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

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;
    assert !PoolChunk.isSubpage(handle) ||
        chunk.arena.sizeClass.size2SizeIdx(maxLength) <= chunk.arena.sizeClass.smallMaxSizeIdx:
    "Allocated small sub-page handle for a buffer size that isn't \"small.\"";

    chunk.incrementPinnedMemory(maxLength);
    this.chunk = chunk;
    memory = chunk.memory;
    tmpNioBuf = nioBuffer;
    allocator = chunk.arena.parent;
    this.cache = cache;
    this.handle = handle;
    this.offset = offset;
    this.length = length;
    this.maxLength = maxLength;
}

最后进入到init0对Buf进行初始化。

附PoolChunk内存模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/-----------------\
| run             |
|                 |
|                 |
|-----------------|
| run             |
|                 |
|-----------------|
| unalloctated    |
| (freed)         |
|                 |
|-----------------|
| subpage         |
|-----------------|
| unallocated     |
| (freed)         |
| ...             |
| ...             |
| ...             |
|                 |
|                 |
|                 |
\-----------------/

Subpage缓存

// TODO:

现在来看看subpage缓存的分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void tcacheAllocateSmall(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity,
                                 final int sizeIdx) {

    // 尝试从缓存中分配小型内存块
    if (cache.allocateSmall(this, buf, reqCapacity, sizeIdx)) {
        // 如果成功从缓存中分配内存,则直接返回
        return;
    }

    /*
         * 在头部上进行同步。这是必要的,因为 {@link PoolChunk#allocateSubpage(int)} 和
         * {@link PoolChunk#free(long)} 可能会修改双向链表。
         */
    final PoolSubpage<T> head = smallSubpagePools[sizeIdx]; // 获取对应大小索引的头部
    final boolean needsNormalAllocation; // 标记是否需要正常分配
    head.lock(); // 锁定头部以进行安全操作
    try {
        final PoolSubpage<T> s = head.next; // 获取下一个子页面
        needsNormalAllocation = s == head; // 检查是否需要正常分配
        if (!needsNormalAllocation) {
            // 确保子页面未被销毁且元素大小与索引匹配
            assert s.doNotDestroy && s.elemSize == sizeClass.sizeIdx2size(sizeIdx) : "doNotDestroy=" +
                s.doNotDestroy + ", elemSize=" + s.elemSize + ", sizeIdx=" + sizeIdx;
            long handle = s.allocate(); // 从子页面分配内存
            assert handle >= 0; // 确保分配成功
            s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache); // 初始化缓冲区
        }
    } finally {
        head.unlock(); // 解锁头部
    }

    // 如果需要正常分配,则进行正常分配
    if (needsNormalAllocation) {
        lock(); // 锁定以进行安全操作
        try {
            allocateNormal(buf, reqCapacity, sizeIdx, cache); // 调用正常分配方法
        } finally {
            unlock(); // 解锁
        }
    }

    incSmallAllocation(); // 增加小型分配计数
}

确定使用subpage分配后long handle = s.allocate();开始进行分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
long allocate() {
    // 检查是否有可用的元素,或者是否允许销毁
    if (numAvail == 0 || !doNotDestroy) {
        return -1; // 如果没有可用元素或不允许销毁,返回 -1
    }

    // 获取下一个可用的位图索引
    final int bitmapIdx = getNextAvail();
    // 检查位图索引是否有效
    if (bitmapIdx < 0) {
        removeFromPool(); // 如果位图索引无效,移除子页面以防止重复错误
        throw new AssertionError("No next available bitmap index found (bitmapIdx = " + bitmapIdx + "), " +
                                 "even though there are supposed to be (numAvail = " + numAvail + ") " +
                                 "out of (maxNumElems = " + maxNumElems + ") available indexes.");
    }
    // 计算位图索引的行和列
    int q = bitmapIdx >>> 6; // 行索引
    int r = bitmapIdx & 63;   // 列索引
    // 确保该位图位置未被占用
    assert (bitmap[q] >>> r & 1) == 0;
    // 标记该位图位置为已占用
    bitmap[q] |= 1L << r;

    // 减少可用元素的数量,并检查是否为零
    if (-- numAvail == 0) {
        removeFromPool(); // 如果没有可用元素,移除子页面
    }

    // 返回位图索引的句柄
    return toHandle(bitmapIdx);
}

private long toHandle(int bitmapIdx) {
    int pages = runSize >> pageShifts;
    return (long) runOffset << RUN_OFFSET_SHIFT
        | (long) pages << SIZE_SHIFT
        | 1L << IS_USED_SHIFT
        | 1L << IS_SUBPAGE_SHIFT
        | bitmapIdx;
}

分配完成后开始初始化

1
2
3
4
5
6
7
8
9
10
11
12
void initBufWithSubpage(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity,
                        PoolThreadCache threadCache) {
    int runOffset = runOffset(handle);
    int bitmapIdx = bitmapIdx(handle);

    PoolSubpage<T> s = subpages[runOffset];
    assert s.isDoNotDestroy();
    assert reqCapacity <= s.elemSize : reqCapacity + "<=" + s.elemSize;

    int offset = (runOffset << pageShifts) + bitmapIdx * s.elemSize;
    buf.init(this, nioBuffer, handle, offset, reqCapacity, s.elemSize, threadCache);
}

init方法最后会调用上面提到的init0方法。

本文由作者按照 CC BY 4.0 进行授权