在上一篇文章
(二) 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字节缓冲。
接下来来看看UnpooledByteBufAllocator的这几个方法是怎么分配ByteBuf的,首先上类的继承关系图:
图1. UnpooledByteBufAllocator的继承关系
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中如何实现。
考察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);
}
解释:
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。
当ByteBuf内存大小不够用、需扩充时,可以调用int calculateNewCapacity(int minNewCapacity, int maxCapacity)方法获取到应该扩充的大小,而不是自己胡乱指定大小,这也算是使用netty ByteBuf时的一个最佳实践。(当然你非常了解自己应用的内存使用规律时,完全可以自己决定ByteBuf容量如何增长。)简单说明下,minNewCapacity是应用所需要的最小容量,calculateNewCapacity返回的是字节缓冲真正的大小。
calculateNewCapacity的基本策略是: