一,获取
1.ByteBuf实现分类
按底层实现分类
1.HeadByteBuf: 底层实现为java堆内的字节数组,byte[],可以有GC回收。特点是内涵的分配和回收速度块,缺点是如果进行socket的io读写,需要额外做一次内存复制,将堆内存的内容复制到内核内存中,性能会有一定程度的下降。
2.DirectByteBuf:直接内存字节缓冲区,非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收都相对较慢,但是读写操作,少了一次内存复制,速度比堆内存块。内核空间的字节数组,native堆中,由操作系统申请和管理,所以分配和释放速度较慢,因为调用方法要走更多的流程。
3.CompositeByteBuf:上面两种方式的组合,也是一种零拷贝技术。内部实现组合两个缓冲区。
按内存回收角度分类:
1.UnpooledByteBuf,没有使用对象池的普通bytebuf,
2.PooledByteBuf:使用了对象池的bytebuf,避免了bytebuf的重复分配,回收,减少了GC的负担,但是内存池的管理和维护更加复杂,netty4.1默认使用对象池缓冲区。4.0默认使用非对象池缓冲区。
2.创建ByteBuf
ByteBuf的Allocator分配器:创建缓冲区和分配内存空间,提供了两种实现
1.PoolByteBufAllocator:将ByteBuf实例放入池对象中,提高性能,将内存碎片减少到最小。
2.UnpooledByteBufAllocator是普通未池化ByteBuf分配器,不会把ByteBuf放入池中,每次调用返回一个新的ByteBuf实例。
说明:我们先从 非池化的开始介绍,再介绍池化的,从简单到难
2.1 UnpooledHeadByteBuf
它分配的内存就在jvm管理的堆中,可以说是最简单的一个ByteBuf,它的内部就是一个byte[]组。
字段:
private final ByteBufAllocator alloc; //非池化 分配器
byte[] array;
private ByteBuffer tmpNioBuf;
我们先看它是如何通过xxxAllocator分配出来的
1.UnpooledByteBufAllocator.newHeapBuffer()方法
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
return PlatformDependent.hasUnsafe() ?
new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
这里我们走第一个new对象,它是xxAllocator中的内部类,也就是本类,它的特点就是继承了我们需要实例化的对象,
private static final class InstrumentedUnpooledUnsafeHeapByteBuf extends UnpooledUnsafeHeapByteBuf {
InstrumentedUnpooledUnsafeHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(alloc, initialCapacity, maxCapacity);
}
它的构造方法,就是直接调用父类的构造方法,所以,我们就拿到了UnpooledHeapByteBuf的子类了。
2.UnpooledHeapByteBuf 构造方法
它有两个构造方法,一个是根据大小自己初始化一个Byte[]数组,另外一个是直接赋值Byte[]数组,不用自己再去初始化。
public UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(maxCapacity);
if (initialCapacity > maxCapacity) { //抛出异常
....
}
this.alloc = checkNotNull(alloc, "alloc"); //检测是否为空
setArray(allocateArray(initialCapacity)); //申请 创建一个初始的数组
setIndex(0, 0);
}
重点方法就在这个SetArray上面,它是嵌套了,里面有一个分配的方法
直接,简单
protected byte[] allocateArray(int initialCapacity) {
return new byte[initialCapacity];
}
private void setArray(byte[] initialArray) {
array = initialArray;
tmpNioBuf = null;
}
ByteBuf初始化之后,我们再来看它的读取
3.get/set方法
随机读取getInt()
public int getInt(int index) {
ensureAccessible();
return _getInt(index);
}
protected int _getInt(int index) {
return HeapByteBufUtil.getInt(array, index); //使用工具类
}
工具类HeapByteBufUtil的具体实现,一个Int占4个byte。
static int getInt(byte[] memory, int index) {
return (memory[index] & 0xff) << 24 |
(memory[index + 1] & 0xff) << 16 |
(memory[index + 2] & 0xff) << 8 |
memory[index + 3] & 0xff;
}
setInt的具体实现
static void setInt(byte[] memory, int index, int value) {
memory[index] = (byte) (value >>> 24);
memory[index + 1] = (byte) (value >>> 16);
memory[index + 2] = (byte) (value >>> 8);
memory[index + 3] = (byte) value;
}
4.ByteBuf转ByteBuffer
public ByteBuffer nioBuffer(int index, int length) {
ensureAccessible();
return ByteBuffer.wrap(array, index, length).slice();
}
跳转到ByteBuffer中的wrap打包方法
public static ByteBuffer wrap(byte[] array,int offset, int length){
try {
return new HeapByteBuffer(array, offset, length); //直接new一个出来
...
}
直接就根据内容,new了一个Nio ByteBuffer出来,转换逻辑简单。
2.2 UnpooledDirectByteBuf
它使用直接内存,交给操作系统分配,所以相比于Heap来说,复杂一些
字段:
private final ByteBufAllocator alloc;
ByteBuffer buffer; // accessed by UnpooledUnsafeNoCleanerDirectByteBuf.reallocateDirect()
private ByteBuffer tmpNioBuf;
private int capacity;
private boolean doNotFree; //是否释放
它与HeapByteBuf的直接不同点就是,它使用的是nio ByteBuffer来存在了,而HeapByteBuf使用的是数组。
1.newDirectBuffer()
位于UnpooledByteBufAllocator,非池化的ByteBuf的创建,都在这个分配器里面
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);
}
还是一样的套路,内部类,我们看unsafeNoClearDirectByteBuf这个,调用父类构造方法,直接super(),来到我们的目标类中,先检测字段是否合法,最后直接allocateDirect()方法构建
public UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
super(maxCapacity);
ObjectUtil.checkNotNull(alloc, "alloc");
checkPositiveOrZero(initialCapacity, "initialCapacity");
checkPositiveOrZero(maxCapacity, "maxCapacity");
if (initialCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
}
this.alloc = alloc;
setByteBuffer(allocateDirect(initialCapacity), false);
}
它调用的是ByteBuffer的allocateDirect()方法
protected ByteBuffer allocateDirect(int initialCapacity) {
return ByteBuffer.allocateDirect(initialCapacity);
}
这个方法,直接返回一个nio的 DirectByteBuffer,所以在我们的目标类中,字段是nio的ByteBuffer就是这个原因,我们保存的是nio的DirectByteBuffer类
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
它的继承关系
来看DirectByteBuffer类的构造方法,传入的是初始大小
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned(); //是否使用 页对齐
int ps = Bits.pageSize(); //页大小,默认是4086b,4kb
long size = Math.max(1L, (long)cap + (pa ? ps : 0)); //如果使用页对齐,那么在基本的大小cap上,还要+页大小
Bits.reserveMemory(size, cap); //尝试申请内存 ★
long base = 0;
try {
base = unsafe.allocateMemory(size); //分配内存,返回基地址
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);//没内存可分配,回滚修改的内存数据 throw x;
}
unsafe.setMemory(base, size, (byte) 0); //设置内存里的初始值为0
if (pa && (base % ps != 0)) { //地址对齐,且基地址不是页大小的整数倍
// Round up to page boundary
address = base + ps - (base & (ps - 1)); //将地址改为页大小的整数倍
} else {
address = base; //地址就是基地址
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //创建内存回收器,传的是基地址
att = null; //没附件
}
页对齐的,是使用空间换时间,前面一个页没有使用完毕,那么我们新加入的数据,可能会一部分放在前面一个页,一部分放在新页上面,我们增加一个页空间,不管什么情况,我们都可以把自己全部内容换到一个新的页,占用该页的开头位置,(多余的空间就用来填补前一个页余下的空间,这里就是浪费空间的意思),而之前如果一个数据放在两个页上面就会加载两次,现在我们只需要加载一次,就达到了空间换时间的目的。
具体的分配:Bits.reserveMemory()
static void reserveMemory(long size, int cap) {
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory(); //获取分配的上限
memoryLimitSet = true;
}
// optimist! 看是否还能申请内存,成功就返回
if (tryReserveMemory(size, cap)) {
return;
}
final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
// retry while helping enqueue pending Reference objects
// which includes executing pending Cleaner(s) which includes
// Cleaner(s) that free direct buffer memory
while (jlra.tryHandlePendingReference()) {
if (tryReserveMemory(size, cap)) { //再次尝试申请
return;
}
}
System.gc(); //如果没有成功,就启动gc
// a retry loop with exponential back-off delays
// (this gives VM some time to do it's job)
boolean interrupted = false;
try {
long sleepTime = 1;
int sleeps = 0;
while (true) {
if (tryReserveMemory(size, cap)) {
return;
}
if (sleeps >= MAX_SLEEPS) { //尝试等待9次睡眠,大概0.5秒,如果还没内存就退出循环
break;
}
if (!jlra.tryHandlePendingReference()) {//如果没有释放内存就sleep
try {
Thread.sleep(sleepTime);
sleepTime <<= 1; //睡眠时间x2
sleeps++;
} catch (InterruptedException e) {
interrupted = true;
}
}
}
// no luck 没有内存可以申请,抛出异常
throw new OutOfMemoryError("Direct buffer memory");
} finally {
if (interrupted) {
// don't swallow interrupts
Thread.currentThread().interrupt();
}
}
}
这里就要涉及底层的东西了,我们先到这里,详细内容,可以看下方的参考链接,超级详细。
2.3 池化ByteBuf
这个内容,就更加复杂了,也可以直接参考下方的链接,我就不当搬运工了!
二,释放
在流水线的末尾添加TailHandler处理器,进行自动释放。
但是如果流水线没有走完,而是走到了一半就截断了,这个时候要嘛就是手动释放,
或者SimpleChannelInbounHandler进行释放。
一共上述三种情况。
(自动释放)https://blog.csdn.net/wangwei19871103/article/details/104679073
参考链接:https://blog.csdn.net/wangwei19871103/article/details/104235590
https://blog.csdn.net/wangwei19871103/article/details/104264130