ByteBuf分类,从下面三个维度:
1、Pooled和Unpooled
2、Unsafe和非Unsafe
3、Heap和Direct
一、Pooled和Unpooled
这两个的区别是使用的内存是从预先分配好的内存还是未分配的内存取,第一个从预先分配好的内存去取一段连续的内存,第二种调用系统api直接去申请一块内存。
在分配bytebuf的时候,会有一个分配器:
分配器的两个子类实现UnpooledByteBufAllocator和PooledByteBufAllocator就能够实现pool和unpool内存的分配。后面我们会继续深入。
说得更加直白一点:pooled的ByteBuf基于内存池可以重复利用
二、Unsafe和非Unsafe
(通过指针)直接操作对象的内存地址还是通过安全的方式操作内存(直接操作物理内存),如果是unsafe就可以拿到bytebuf在jvm的内存调用jdk的unsafe直接去操作(通过jdk的API)。
我们之前知道,AbstractByteBuf会有个最终去执行getByte的方法 protected abstract byte _getByte(int index); 这些下划线开头的方法,最终都由他的子类去实现。我们先看一个子类的实现UnpooledUnsafeHeapByteBuf,他是一个unsafe的实现,看他实现的_getByte()方法:
@Override
protected byte _getByte(int index) {
return UnsafeByteBufUtil.getByte(array, index);
}
他是用的UnsafeByteBufUtil去实现的,我们后面会知道,所有的unsafebytebuf都会通过这个工具类去操作底层的unsafe,继续一直深入下去,就会看到是用底层的unsafe去实现的:
static byte getByte(byte[] data, int index) {
return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);
}
然后我们看UnpooledUnsafeByteBuf的_getByte()的实现:
@Override
protected byte _getByte(int index) {
return HeapByteBufUtil.getByte(array, index);
}
他是通过HeapByteBufUtil去实现的,继续深入:
static byte getByte(byte[] memory, int index) {
return memory[index];
}
对比他们两种实现,我们总结:unsafe通过操作底层unsafe的offset+index的方式去操作数据,非unsafe直接通过一个数组的下标(或者jdk底层的buffer)去操作数据。
unsafe需要依赖到jdk底层的unsafe对象,非unsafe不需要。
三、Heap和Direct
操作jvm的堆内存还是直接内存,如果是direct内存不受jvm控制,所以也不会回收,需要自己去回收。
我们以UnpooledHeapByteBuf和UnpooledDirectByteBuf为例,介绍他们之间的区别。
首先看UnpooledHeapByteBuf源码:
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
private final ByteBufAllocator alloc;
byte[] array;
private ByteBuffer tmpNioBuf;
...
}
看到array,我们知道heap的话,它的数据就存放在array里面,所有内存相关的操作就在这个array上进行。上一部分我们知道,_getByte的时候,就是操作一个数组(通过数组下标的方式),这个数组就是这个array。
然后看UnpooledDirectByteBuf源码:
*/
public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf {
private final ByteBufAllocator alloc;
private ByteBuffer buffer;
private ByteBuffer tmpNioBuf;
private int capacity;
private boolean doNotFree;
...
}
他是通过一个ByteBuffer来存储数据,ByteBuffer何许人也?
我们查看它的实现,能够看到有一个:DirectByteBuffer,这个是jdk的nio底层分配的buffer,用于操作堆外内存。
我们_getByte的时候,就是通过这个buffer去实现的:
@Override
protected byte _getByte(int index) {
return buffer.get(index);
}
内存分配就是通过Unpooled实现的。
现在我们分析unpooled,我们找到这个父类:
public final class Unpooled {
...
/**
* Creates a new big-endian direct buffer with the specified {@code capacity}, which
* expands its capacity boundlessly on demand. The new buffer's {@code readerIndex} and
* {@code writerIndex} are {@code 0}.
*/
public static ByteBuf directBuffer(int initialCapacity) {
return ALLOC.directBuffer(initialCapacity);
}
...
}
我们可以知道,分配内存就是通过这个去实现的。继续看ALLOC的AbstractByteBufAllocator的实现:
@Override
public ByteBuf directBuffer() {
return directBuffer(DEFAULT_INITIAL_CAPACITY, Integer.MAX_VALUE);
}
继续看UnpooledByteBufAllocator的newDirectBuffer方法的实现:
@Override
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
ByteBuf buf = PlatformDependent.hasUnsafe() ?
UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
}
先说个题外话,我们看到是否unsafe不是我们自己决定的,而是由jdk实现的,如果能够获取到unsafe对象,就使用unsafe,反之亦然。然后我们分析的是UnpooledDirectByteBuf,继续:
/**
* Creates a new direct buffer.
*
* @param initialCapacity the initial capacity of the underlying direct buffer
* @param maxCapacity the maximum capacity of the underlying direct buffer
*/
protected UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(maxCapacity);
if (alloc == null) {
throw new NullPointerException("alloc");
}
if (initialCapacity < 0) {
throw new IllegalArgumentException("initialCapacity: " + initialCapacity);
}
if (maxCapacity < 0) {
throw new IllegalArgumentException("maxCapacity: " + maxCapacity);
}
if (initialCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
}
this.alloc = alloc;
setByteBuffer(ByteBuffer.allocateDirect(initialCapacity));
}
我们这个看到一个allocateDirect(initialCapacity),一层层进去,就能看到jdk的nio底层创建ByteBuffer的方法:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
然后通过setByteBuffer方法,把分配的内存设置到buffer变量里面去:
private void setByteBuffer(ByteBuffer buffer) {
ByteBuffer oldBuffer = this.buffer;
if (oldBuffer != null) {
if (doNotFree) {
doNotFree = false;
} else {
freeDirect(oldBuffer);
}
}
this.buffer = buffer;
tmpNioBuf = null;
capacity = buffer.remaining();
}
总结,heap是依赖一个数组,direct是依赖jdk底层的ByteBuffer。