Netty ByteBuf 的扩容机制主要由 AbstractByteBuf 类及其子类中的 ensureWritable 方法及其相关逻辑控制,并受到 ByteBufAllocator 和 RecvByteBufAllocator(特别是 AdaptiveRecvByteBufAllocator)的影响。
1. 扩容触发条件
当调用 ByteBuf
的写入方法(如 writeBytes
、writeInt
等),如果写入的数据量超过了当前 ByteBuf
的可写容量(writableBytes()
),就会触发扩容操作。以下是一个简单示例:
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class ByteBufExpandExample {
public static void main(String[] args) {
ByteBuf buffer = Unpooled.buffer(10); // 初始容量为 10
System.out.println("初始容量: " + buffer.capacity());
byte[] data = new byte[20];
try {
buffer.writeBytes(data); // 尝试写入 20 字节数据,会触发扩容
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("扩容后容量: " + buffer.capacity());
buffer.release();
}
}
2. 核心扩容方法
ByteBuf
的扩容主要通过 capacity(int newCapacity)
方法实现,不同的 ByteBuf
实现类会重写该方法来实现具体的扩容逻辑。以下是一些常见 ByteBuf
实现类的 capacity
方法:
2.1 UnpooledHeapByteBuf
@Override
public ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity);
byte[] oldArray = array;
int oldCapacity = oldArray.length;
if (newCapacity == oldCapacity) {
return this;
}
int bytesToCopy;
if (newCapacity > oldCapacity) {
bytesToCopy = oldCapacity;
} else {
trimIndicesToCapacity(newCapacity);
bytesToCopy = newCapacity;
}
byte[] newArray = allocateArray(newCapacity);
System.arraycopy(oldArray, 0, newArray, 0, bytesToCopy);
setArray(newArray);
freeArray(oldArray);
return this;
}
- 逻辑说明:首先检查新容量是否合法,然后比较新容量和旧容量。如果新容量大于旧容量,将旧数组中的数据复制到新数组;如果新容量小于旧容量,先调整读写索引,再复制数据。最后释放旧数组,设置新数组。
2.2 PooledByteBuf
@Override
public final ByteBuf capacity(int newCapacity) {
if (newCapacity == length) {
ensureAccessible();
return this;
}
checkNewCapacity(newCapacity);
if (!chunk.unpooled) {
if (newCapacity > length) {
if (newCapacity <= maxLength) {
length = newCapacity;
return this;
}
} else if (newCapacity > maxLength >>> 1 &&
(maxLength > 512 || newCapacity > maxLength - 16)) {
length = newCapacity;
trimIndicesToCapacity(newCapacity);
return this;
}
}
PooledByteBufAllocator.onReallocateBuffer(this, newCapacity);
chunk.arena.reallocate(this, newCapacity);
return this;
}
- 逻辑说明:先检查新容量是否等于当前长度,如果是则直接返回。然后检查新容量是否在合适的范围内,如果是则更新长度并返回。否则,调用
PooledByteBufAllocator
和ChunkArena
的相关方法进行重新分配。
3. 计算新容量的策略
在进行扩容时,需要计算新的容量。ByteBufAllocator
提供了 calculateNewCapacity
方法来计算新容量,不同的 ByteBufAllocator
实现类会重写该方法以实现不同的计算逻辑。例如,AbstractByteBufAllocator
的实现如下:
@Override
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));
}
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 <= newCapacity 是 2 的幂次方 <= 阈值
final int newCapacity = MathUtil.findNextPositivePowerOfTwo(Math.max(minNewCapacity, 64));
return Math.min(newCapacity, maxCapacity);
}
-
逻辑说明
:
- 首先检查最小新容量是否合法,是否超过最大容量。
- 如果最小新容量等于阈值(4 MiB),则直接返回阈值。
- 如果最小新容量超过阈值,不进行双倍扩容,而是按阈值增加。
- 如果最小新容量小于阈值,将其调整为不小于 64 且是 2 的幂次方的数。
- 最后返回不超过最大容量的新容量。
4. 总结
Netty 的 ByteBuf
扩容机制通过不同的实现类和方法,提供了灵活且高效的内存管理方案。在实际使用中,可以根据具体的需求选择合适的 ByteBuf
实现类和扩容策略,以提高系统的性能和稳定性。同时,了解扩容机制有助于更好地使用 ByteBuf
,避免不必要的内存开销和性能问题。