Netty中的ByteBuf优势

NIO使用的ByteBuffer有哪些缺点

1: 无法动态扩容,ByteBuffer的长度是固定的,是初始指定的值,不能够再进行扩容了,当写入的内容大于ByteBuffer的容量时,会报越界异常

2.: API使用复杂,当要读取数据时,需要调用buffer.flip()方法,转换为读取模式,如果稍微不注意就可能出现错误,读取不到数据或者读取的数据是错误的

ByteBuf的优势和做了哪些增强

1: API操作起来更加的方便,可以直接写或者直接读

2:支持动态扩容,当写入的数据大于ByteBuf的容量时,会动态扩容,不会报错

3:提供了多种ByteBuf的实现,可以更加灵活的使用

4:提供了高效的零拷贝机制

5:ByteBuf可以内存复用

ByteBuf操作示例

ByteBuf操作

ByteBuf中有三个重要的属性:

1:capacity容量,初始指定的ByteBuf的大小

2:readIndex读取位置,顺序读的时候,记录读取数据的索引值

3:writeIndex写入位置,顺序写的时候,记录写入数据的索引值

ByteBuf常用的方法:

1:getByte和setByte,获取指定索引处的数据,是随机获取的,不会改变readIndex和writeIndex的值

2:read,顺序读,会改变readIndex的值

3:write
,顺序写,会改变writeIndex的值

4:discardReadBytes,清除读过的内容

5:clear,清除缓冲区

6:搜索操作

7:标记和重置

8:引用计数和释放

简单的Demo示例

  1. /**
  2. * ByteBuf的使用示例
  3. */
  4. public class ByteBufDemo {
  5. public static void main(String[] args) {
  6. //分配非池化,10个字节的ByteBuf
  7. ByteBuf buf = Unpooled.buffer(10);
  8. //看下ByteBuf
  9. System.out.println("------------------------原始的ByteBuf-------------------------------");
  10. System.out.println("ByteBuf参数:" + buf.toString());
  11. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  12. //写入内容到ByteBuf
  13. byte[] bytes = {1, 2, 3, 4, 5};
  14. buf.writeBytes(bytes);
  15. System.out.println("------------------------写入内容后的ByteBuf-------------------------------");
  16. System.out.println("ByteBuf参数:" + buf.toString());
  17. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  18. //从ByteBuf中读取内容
  19. buf.readByte();
  20. buf.readByte();
  21. System.out.println("------------------------读取一些内容后的ByteBuf-------------------------------");
  22. System.out.println("ByteBuf参数:" + buf.toString());
  23. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  24. //清除读过的内容
  25. //把读过的数据清除后,readIndex变为0,writeIndex变为3
  26. //后面尚未读取的内容,会复制到前面去,把原来的值覆盖掉
  27. //再次写入时,3,4,5后面的4,5会被覆盖掉
  28. buf.discardReadBytes();
  29. System.out.println("------------------------清除读过的数据后的ByteBuf-------------------------------");
  30. System.out.println("ByteBuf参数:" + buf.toString());
  31. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  32. //再次写入内容到ByteBuf
  33. byte[] bytesO = {6};
  34. buf.writeBytes(bytesO);
  35. System.out.println("------------------------再次写入内容后的ByteBuf-------------------------------");
  36. System.out.println("ByteBuf参数:" + buf.toString());
  37. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  38. //清空读和写的索引值
  39. //readIndex和writeIndex会重置为0,ByteBuf中的内容并不会重置
  40. buf.clear();
  41. System.out.println("------------------------清空读和写的索引值后的ByteBuf-------------------------------");
  42. System.out.println("ByteBuf参数:" + buf.toString());
  43. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  44. //再次写入内容到ByteBuf
  45. byte[] bytes2 = {1, 2, 3};
  46. buf.writeBytes(bytes2);
  47. System.out.println("------------------------再次写入内容后的ByteBuf-------------------------------");
  48. System.out.println("ByteBuf参数:" + buf.toString());
  49. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  50. //清空ByteBuf的内容
  51. //不会重置readIndex和writeIndex
  52. buf.setZero(0, buf.capacity());
  53. System.out.println("------------------------清空ByteBuf的内容后的ByteBuf-------------------------------");
  54. System.out.println("ByteBuf参数:" + buf.toString());
  55. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  56. //再次写入超出指定容量的数据到ByteBuf
  57. //会进行扩容
  58. byte[] bytes3 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
  59. buf.writeBytes(bytes3);
  60. System.out.println("------------------------再次写入超出指定容量的数据后的ByteBuf-------------------------------");
  61. System.out.println("ByteBuf参数:" + buf.toString());
  62. System.out.println("ByteBuf中的内容:" + Arrays.toString(buf.array()) + "\n");
  63. }
  64. }

输出结果:



上面的例子是使用堆内的ByteBuf,下面看下堆外的ByteBuf例子:

  1. //分配非池化,10个字节的directBuffer
  2. ByteBuf buf = Unpooled.directBuffer(10);
  3. //看下ByteBuf
  4. System.out.println("------------------------原始的ByteBuf-------------------------------");
  5. System.out.println("ByteBuf参数:" + buf.toString());

directBuffer不能够使用array方法,否则会报错:java.lang.UnsupportedOperationException: direct buffer;而且使用ByteBuf是用它底层的分配器分配的,不是new一个出来,下面会具体说下。



上图中,可以看到,readIndex和writeIndex把缓冲区分成了三块,readIndex会小于或者等于writeIndex,这个应该好理解,我还没有写到那里,你就去读取了,能读取到什么呢。

堆内和堆外内存



socket是操作系统底层提供给上层应用使用的网络通信API,当要去读取或者写入的数据在JVM的堆中,那么就先需要把JVM堆中需要读取的数据拷贝一份到操作系统中,然后socket再去读取,而直接内存的好处是socket可以直接读取,少了拷贝这一步操作。

ByteBuf动态扩容

下面以堆内的ByteBuf为例,查看源码,分析ByteBuf的动态扩容:

动态扩容肯定是写入数据的时候,ByteBuf的容量不够了,才去扩容的,所以需要跟踪下面的代码:

  1. buf.writeBytes(bytes);

跟踪上面的writeBytes,首先进入了ByteBuf这个抽象类中,进入了下面这个抽象方法:

  1. public abstract ByteBuf writeBytes(byte[] src);

它的实现类如下:



进入第一个AbstractByteBuf的方法:

  1. @Override
  2. public ByteBuf writeBytes(byte[] src) {
  3. writeBytes(src, 0, src.length);
  4. return this;
  5. }

再次调用了下面的方法:

  1. @Override
  2. public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
  3. //检查是否可以写入
  4. ensureWritable(length);
  5. setBytes(writerIndex, src, srcIndex, length);
  6. //把当前的写入位置加上写入数据的长度
  7. writerIndex += length;
  8. return this;
  9. }

src是需要写入的数据,length是写入数据的长度

然后会进入ensureWritable方法,传入的参数是:写入数据的长度

  1. @Override
  2. public ByteBuf ensureWritable(int minWritableBytes) {
  3. //参数校验
  4. checkPositiveOrZero(minWritableBytes, "minWritableBytes");
  5. //检查容量是否可以写入这么多数据
  6. ensureWritable0(minWritableBytes);
  7. return this;
  8. }
  1. //检查参数是否小于0
  2. public static int checkPositiveOrZero(int i, String name) {
  3. if (i < 0) {
  4. throw new IllegalArgumentException(name + ": " + i + " (expected: >= 0)");
  5. }
  6. return i;
  7. }

参数校验完成后会进入ensureWritable0方法:

  1. final void ensureWritable0(int minWritableBytes) {
  2. //确保缓冲区可以访问
  3. ensureAccessible();
  4. //如果写入的数据长度小于等于剩余可写数据的容量,就直接返回
  5. //就是说,容量足够写入,不需要扩容
  6. if (minWritableBytes <= writableBytes()) {
  7. return;
  8. }
  9. if (checkBounds) {
  10. //maxCapacity是int的最大值
  11. //检查写入的数据长度是否比可以写入的最大容量还要大
  12. //是的话就抛异常
  13. if (minWritableBytes > maxCapacity - writerIndex) {
  14. throw new IndexOutOfBoundsException(String.format(
  15. "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
  16. writerIndex, minWritableBytes, maxCapacity, this));
  17. }
  18. }
  19. //正式的扩容方法
  20. int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
  21. //把扩容后的新容量设置进去
  22. capacity(newCapacity);
  23. }

进入AbstractByteBufAllocator类的扩容方法:

  1. //常量 4M
  2. static final int CALCULATE_THRESHOLD = 1048576 * 4; // 4 MiB page
  3. @Override
  4. public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
  5. //校验参数
  6. checkPositiveOrZero(minNewCapacity, "minNewCapacity");
  7. //minNewCapacity = writerIndex + minWritableBytes
  8. //已经写入的数据索引加上当前写入的数据长度,就是需要的最小的容量
  9. //判断是否比最大容量还大,是的话就抛异常
  10. if (minNewCapacity > maxCapacity) {
  11. throw new IllegalArgumentException(String.format(
  12. "minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
  13. minNewCapacity, maxCapacity));
  14. }
  15. final int threshold = CALCULATE_THRESHOLD; // 4 MiB page
  16. //如果需要的最小容量等于4M,就直接返回4M,作为扩容后的容量
  17. if (minNewCapacity == threshold) {
  18. return threshold;
  19. }
  20. //如果需要的最小容量大于4M,就按照下面的扩容方式扩容
  21. if (minNewCapacity > threshold) {
  22. //newCapacity = 15 / 4194304 * 4194304
  23. int newCapacity = minNewCapacity / threshold * threshold;
  24. //如果计算出的容量大于最大容量减去4M,就把最大容量赋值给新的容量
  25. if (newCapacity > maxCapacity - threshold) {
  26. newCapacity = maxCapacity;
  27. } else {
  28. newCapacity += threshold;
  29. }
  30. return newCapacity;
  31. }
  32. //如果需要的最小容量小于4M,就按照下面的方式扩容
  33. int newCapacity = 64;
  34. while (newCapacity < minNewCapacity) {
  35. newCapacity <<= 1;
  36. }
  37. return Math.min(newCapacity, maxCapacity);
  38. }

再看下capacity方法:

下面的把扩容后的容量放到ByteBuf,就是使用了arraycopy方法

  1. @Override
  2. public ByteBuf capacity(int newCapacity) {
  3. checkNewCapacity(newCapacity);
  4. int oldCapacity = array.length;
  5. byte[] oldArray = array;
  6. if (newCapacity > oldCapacity) {
  7. byte[] newArray = allocateArray(newCapacity);
  8. System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
  9. setArray(newArray);
  10. freeArray(oldArray);
  11. } else if (newCapacity < oldCapacity) {
  12. byte[] newArray = allocateArray(newCapacity);
  13. int readerIndex = readerIndex();
  14. if (readerIndex < newCapacity) {
  15. int writerIndex = writerIndex();
  16. if (writerIndex > newCapacity) {
  17. writerIndex(writerIndex = newCapacity);
  18. }
  19. System.arraycopy(oldArray, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
  20. } else {
  21. setIndex(newCapacity, newCapacity);
  22. }
  23. setArray(newArray);
  24. freeArray(oldArray);
  25. }
  26. return this;
  27. }

下面是跟踪的代码步骤:



总结下动态扩容机制:

1:write*方法调用的时候,会通过ensureWritable0方法检查

2:calculateNewCapacity方法是用来计算容量的方法

扩容计算方法:

1:需要的容量没有超过4M,会从64字节开始扩容,每次增加一倍,直到计算出来的容量满足需要的最小容量,假如,当前大小是256,已经写入了200字节,再次写入60字节,需要的最小容量是260字节,那么扩容后的容量是64 * 2 * 2 * 2=512

2:需要的容量超过4M,扩容计算方法为:新容量 = 新容量的最小要求 / 4M * 4M + 4M,假如当前大小是3M,已经写了2M,再写入3M,需要的最小容量是5M,那么扩容后的容量是 5 / 4 * 4 + 4 = 8M

图示1:需要的容量小于4M:

图示2:需要的容量大于4M:

ByteBuf有哪些实现

ByteBuf从3个维度,有8种实现方式:

ByteBuf类图

  1. //堆内
  2. ByteBuf buf = Unpooled.buffer(10);
  3. //堆外
  4. ByteBuf buf = Unpooled.directBuffer(10);

ByteBuf提供了Unpooled非池化的类,可以直接使用,没有提供Pool池化的类,下面追踪源码看下ByteBuf是怎样分配的:

Unpooled.buffer分配方式

首先进入Unpooled类:

  1. private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT;
  2. //使用默认的分配器分配堆内buffer
  3. public static ByteBuf buffer(int initialCapacity) {
  4. return ALLOC.heapBuffer(initialCapacity);
  5. }

下面会进入接口类ByteBufAllocator:

  1. //分配一个指定容量的堆内buf
  2. ByteBuf heapBuffer(int initialCapacity);

然后进入AbstractByteBufAllocator抽象类:

  1. //如果没有指定初始容量,默认的初始容量大小是256
  2. static final int DEFAULT_INITIAL_CAPACITY = 256;
  3. //最大容量,为int的最大值
  4. static final int DEFAULT_MAX_CAPACITY = Integer.MAX_VALUE;
  5. @Override
  6. public ByteBuf heapBuffer(int initialCapacity) {
  7. return heapBuffer(initialCapacity, DEFAULT_MAX_CAPACITY);
  8. }
  9. @Override
  10. public ByteBuf heapBuffer(int initialCapacity, int maxCapacity) {
  11. //如果初始化的容量是0,最大的容量也是0,就返回一个空的Buf
  12. if (initialCapacity == 0 && maxCapacity == 0) {
  13. return emptyBuf;
  14. }
  15. validate(initialCapacity, maxCapacity);
  16. return newHeapBuffer(initialCapacity, maxCapacity);
  17. }
  18. //校验参数
  19. private static void validate(int initialCapacity, int maxCapacity) {
  20. //检查参数
  21. checkPositiveOrZero(initialCapacity, "initialCapacity");
  22. //如果初始化的容量大于最大容量,就抛异常
  23. if (initialCapacity > maxCapacity) {
  24. throw new IllegalArgumentException(String.format(
  25. "initialCapacity: %d (expected: not greater than maxCapacity(%d)",
  26. initialCapacity, maxCapacity));
  27. }
  28. }

然后是newHeapBuffer抽象方法:

  1. protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity);

因为这里初始化的是非池化的,所以会进入UnpooledByteBufAllocator类:

  1. @Override
  2. protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
  3. //PlatformDependent.hasUnsafe()是检查当前操作系统是否支持unsafe操作
  4. //根据支持与否,进入不同的类
  5. return PlatformDependent.hasUnsafe() ?
  6. new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
  7. new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
  8. }

支持Unsafe操作的进入下面:

  1. InstrumentedUnpooledUnsafeHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
  2. super(alloc, initialCapacity, maxCapacity);
  3. }

不支持Unsafe的进入下面这个:

  1. InstrumentedUnpooledHeapByteBuf(UnpooledByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
  2. super(alloc, initialCapacity, maxCapacity);
  3. }

现在以支持Unsafe操作往下面走,进入UnpooledUnsafeHeapByteBuf类:

  1. UnpooledUnsafeHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
  2. super(alloc, initialCapacity, maxCapacity);
  3. }

再次调用了父类UnpooledHeapByteBuf:

  1. //分配器
  2. private final ByteBufAllocator alloc;
  3. //byte数组,ByteBuf数据底层就是使用这个存储
  4. byte[] array;
  5. public UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
  6. super(maxCapacity);
  7. //检查分配器是否为空
  8. checkNotNull(alloc, "alloc");
  9. //如果初始化的容量大于最大容量,就抛异常
  10. if (initialCapacity > maxCapacity) {
  11. throw new IllegalArgumentException(String.format(
  12. "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
  13. }
  14. this.alloc = alloc;
  15. //设置当前的数组是分配之后的数组
  16. setArray(allocateArray(initialCapacity));
  17. //初始化readIndex和writeIndex
  18. setIndex(0, 0);
  19. }
  20. //分配数组
  21. protected byte[] allocateArray(int initialCapacity) {
  22. //返回一个具有initialCapacity容量大小的byte数组
  23. return new byte[initialCapacity];
  24. }
  25. //set数组
  26. private void setArray(byte[] initialArray) {
  27. array = initialArray;
  28. tmpNioBuf = null;
  29. }

AbstractByteBuf类下的setIndex方法:

  1. //初始化readerIndex和writerIndex
  2. @Override
  3. public ByteBuf setIndex(int readerIndex, int writerIndex) {
  4. if (checkBounds) {
  5. checkIndexBounds(readerIndex, writerIndex, capacity());
  6. }
  7. setIndex0(readerIndex, writerIndex);
  8. return this;
  9. }
  10. final void setIndex0(int readerIndex, int writerIndex) {
  11. this.readerIndex = readerIndex;
  12. this.writerIndex = writerIndex;
  13. }

上面走到AbstractByteBuf后,就分配完了一个非池化、堆内的ByteBuf,下面是追踪的代码:



总结:

可以看到,分配一个非池化、堆内的ByteBuf,它的底层就是byte数组

Unpooled.directBuffer分配方式

首先进入的也是Unpooled类:

  1. public static ByteBuf directBuffer(int initialCapacity) {
  2. return ALLOC.directBuffer(initialCapacity);
  3. }

然后进入ByteBufAllocator抽象类:

  1. ByteBuf directBuffer(int initialCapacity);

然后到AbstractByteBufAllocator类:

  1. @Override
  2. public ByteBuf directBuffer(int initialCapacity) {
  3. return directBuffer(initialCapacity, DEFAULT_MAX_CAPACITY);
  4. }
  5. @Override
  6. public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
  7. //如果初始化的容量和最大容量都是0,就返回一个空的Buf
  8. if (initialCapacity == 0 && maxCapacity == 0) {
  9. return emptyBuf;
  10. }
  11. //校验参数
  12. validate(initialCapacity, maxCapacity);
  13. return newDirectBuffer(initialCapacity, maxCapacity);
  14. }
  15. protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity);

由于分配的也是一个非池化的,所以newDirectBuffer会进入UnpooledByteBufAllocator类中的实现类:

  1. @Override
  2. protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
  3. final ByteBuf buf;
  4. //同样的,会判断是否支持unsafe操作
  5. if (PlatformDependent.hasUnsafe()) {
  6. buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) :
  7. new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
  8. } else {
  9. buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
  10. }
  11. return disableLeakDetector ? buf : toLeakAwareBuffer(buf);
  12. }

以InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf为例,后面两个其实也相差不大,进入UnpooledUnsafeNoCleanerDirectByteBuf类的构造方法:

  1. UnpooledUnsafeNoCleanerDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
  2. super(alloc, initialCapacity, maxCapacity);
  3. }

再次调用的父类UnpooledUnsafeDirectByteBuf:

  1. ByteBuffer buffer;
  2. public UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
  3. super(maxCapacity);
  4. if (alloc == null) {
  5. throw new NullPointerException("alloc");
  6. }
  7. //校验参数
  8. checkPositiveOrZero(initialCapacity, "initialCapacity");
  9. checkPositiveOrZero(maxCapacity, "maxCapacity");
  10. if (initialCapacity > maxCapacity) {
  11. throw new IllegalArgumentException(String.format(
  12. "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
  13. }
  14. this.alloc = alloc;
  15. setByteBuffer(allocateDirect(initialCapacity), false);
  16. }
  17. //分配的是一个NIO中的ByteBuffer
  18. protected ByteBuffer allocateDirect(int initialCapacity) {
  19. return ByteBuffer.allocateDirect(initialCapacity);
  20. }
  21. final void setByteBuffer(ByteBuffer buffer, boolean tryFree) {
  22. if (tryFree) {
  23. ByteBuffer oldBuffer = this.buffer;
  24. if (oldBuffer != null) {
  25. if (doNotFree) {
  26. doNotFree = false;
  27. } else {
  28. freeDirect(oldBuffer);
  29. }
  30. }
  31. }
  32. this.buffer = buffer;
  33. memoryAddress = PlatformDependent.directBufferAddress(buffer);
  34. tmpNioBuf = null;
  35. capacity = buffer.remaining();
  36. }

ByteBuffer类下面的allocateDirect:

  1. public static ByteBuffer allocateDirect(int capacity) {
  2. return new DirectByteBuffer(capacity);
  3. }

代码跟踪图:



总结:

分配非池化、堆外的ByteBuf,可以看到底层是NIO的DirectByteBuffer实现的

ByteBufAllocator类图

ByteBuf内存复用

分配池化内存

在上面根据源码知道了怎么去分配非池化内存,那么池化内存要怎么分配呢?看下面的图示:



上面就是分配池化内存的步骤,接下来会根据源码具体分析

内存缓存池



jemalloc内存分配机制

1:内存池中有三大区域,分别是:tiny、small、normal

2:每个区域分了不同大小的格子,每个格子只能缓存对应大小的内存块

3:支持最大的格子内存是32kb,超过这个大小的不能被缓存,只能被释放掉

4:每个类型的格子都有对应的数量:tiny:512个,small:256个,normal:64个,例如tiny区域的每个大小的格子都有512个,如果满了就不会被回收,内存会被释放掉

回收池化内存

分配池化内存的过程

上面分析了分配非池化内存,下面看下怎么分配池化内存:

  1. ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
  2. //分配的内存最大长度为496
  3. ByteBuf buf1 = allocator.ioBuffer(495);
  4. System.out.printf("buf1: 0x%X%n", buf1.memoryAddress());
  5. //此时会被回收到tiny的512b格子中
  6. buf1.release();
  7. //从tiny的512b格子去取
  8. ByteBuf buf2 = allocator.ioBuffer(495);
  9. System.out.printf("buf2: 0x%X%n", buf2.memoryAddress());
  10. buf2.release();

先来看下ByteBufAllocator类:

  1. //默认ByteBuf分配器,在ByteBufUtil中初始化
  2. ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

跟踪第一次的allocator.ioBuffer(495)代码,首先进入AbstractByteBufAllocator类:

  1. @Override
  2. public ByteBuf ioBuffer(int initialCapacity) {
  3. //如果支持Unsafe,就分配堆外内存
  4. if (PlatformDependent.hasUnsafe()) {
  5. return directBuffer(initialCapacity);
  6. }
  7. //不支持Unsafe,就分配堆内内存
  8. return heapBuffer(initialCapacity);
  9. }

然后调用了该类下面的directBuffer方法:

  1. @Override
  2. public ByteBuf directBuffer(int initialCapacity) {
  3. return directBuffer(initialCapacity, DEFAULT_MAX_CAPACITY);
  4. }
  5. @Override
  6. public ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
  7. //如果初始化的容量和最大容量等于0,就返回一个空的ByteBuf
  8. if (initialCapacity == 0 && maxCapacity == 0) {
  9. return emptyBuf;
  10. }
  11. validate(initialCapacity, maxCapacity);
  12. return newDirectBuffer(initialCapacity, maxCapacity);
  13. }
  14. //校验参数
  15. private static void validate(int initialCapacity, int maxCapacity) {
  16. checkPositiveOrZero(initialCapacity, "initialCapacity");
  17. if (initialCapacity > maxCapacity) {
  18. throw new IllegalArgumentException(String.format(
  19. "initialCapacity: %d (expected: not greater than maxCapacity(%d)",
  20. initialCapacity, maxCapacity));
  21. }
  22. }

然后会进入池化的ByteBuf分配器PooledByteBufAllocator类,可以实现内存的复用:

  1. // cache sizes 缓存默认值
  2. DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.tinyCacheSize", 512);
  3. DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.smallCacheSize", 256);
  4. DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("io.netty.allocator.normalCacheSize", 64);
  1. @Override
  2. protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
  3. //从当前线程中获取cache对象
  4. PoolThreadCache cache = threadCache.get();
  5. //从cache中获取Arena
  6. //Arena可以理解为一个netty提供的实际进行buf分配和管理的工具
  7. PoolArena<ByteBuffer> directArena = cache.directArena;
  8. final ByteBuf buf;
  9. //如果有directArena就分配池化内存
  10. if (directArena != null) {
  11. buf = directArena.allocate(cache, initialCapacity, maxCapacity);
  12. } else { //如果没有directArena,就使用非池化Unpooled
  13. buf = PlatformDependent.hasUnsafe() ?
  14. UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
  15. new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
  16. }
  17. return toLeakAwareBuffer(buf);
  18. }

再次跟踪后进入PoolArena类:

可以看到下面有三种类型tiny、small、normal

  1. enum SizeClass {
  2. Tiny,
  3. Small,
  4. Normal
  5. }
  1. PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
  2. //获取一个ByteBuf对象
  3. PooledByteBuf<T> buf = newByteBuf(maxCapacity);
  4. //分配内存
  5. allocate(cache, buf, reqCapacity);
  6. return buf;
  7. }
  8. @Override
  9. protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
  10. //如果支持Unsafe,就初始化一个PooledUnsafeDirectByteBuf
  11. if (HAS_UNSAFE) {
  12. return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
  13. } else { //不支持Unsafe,就初始化一个PooledDirectByteBuf
  14. return PooledDirectByteBuf.newInstance(maxCapacity);
  15. }
  16. }

下面进入PooledUnsafeDirectByteBuf类:

从线程回收栈中获取一个buf,如果栈中没有,就会创建一个新的,如果有,就会返回栈中的buf

  1. //调用RECYCLER.get()时,线程栈中没有可以复用的时,会调用newObject方法,此时创建出来的buf是空的
  2. private static final Recycler<PooledUnsafeDirectByteBuf> RECYCLER = new Recycler<PooledUnsafeDirectByteBuf>() {
  3. @Override
  4. protected PooledUnsafeDirectByteBuf newObject(Handle<PooledUnsafeDirectByteBuf> handle) {
  5. return new PooledUnsafeDirectByteBuf(handle, 0);
  6. }
  7. };
  8. static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
  9. //RECYCLER,回收机制
  10. PooledUnsafeDirectByteBuf buf = RECYCLER.get();
  11. //取出来的可能是之前的buf,使用之前清理一下
  12. buf.reuse(maxCapacity);
  13. return buf;
  14. }

然后再次回到PoolArena类中的allocate方法,分配内存:

  1. private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
  2. //将需要的内存大小计算为2^n
  3. final int normCapacity = normalizeCapacity(reqCapacity);
  4. //需要分配的内存是否是tiny或者small类型
  5. if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
  6. int tableIdx;
  7. PoolSubpage<T>[] table;
  8. boolean tiny = isTiny(normCapacity);
  9. if (tiny) { // < 512 //分配一个tiny内存
  10. if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
  11. // was able to allocate out of the cache so move on
  12. return;
  13. }
  14. tableIdx = tinyIdx(normCapacity);
  15. table = tinySubpagePools;
  16. } else {
  17. if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
  18. // was able to allocate out of the cache so move on
  19. return;
  20. }
  21. tableIdx = smallIdx(normCapacity);
  22. table = smallSubpagePools;
  23. }
  24. final PoolSubpage<T> head = table[tableIdx];
  25. synchronized (head) {
  26. final PoolSubpage<T> s = head.next;
  27. if (s != head) {
  28. assert s.doNotDestroy && s.elemSize == normCapacity;
  29. long handle = s.allocate();
  30. assert handle >= 0;
  31. s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity);
  32. incTinySmallAllocation(tiny);
  33. return;
  34. }
  35. }
  36. synchronized (this) {
  37. //分配一块新的内存
  38. allocateNormal(buf, reqCapacity, normCapacity);
  39. }
  40. incTinySmallAllocation(tiny);
  41. return;
  42. }
  43. if (normCapacity <= chunkSize) {
  44. if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
  45. // was able to allocate out of the cache so move on
  46. return;
  47. }
  48. synchronized (this) {
  49. allocateNormal(buf, reqCapacity, normCapacity);
  50. ++allocationsNormal;
  51. }
  52. } else {
  53. // Huge allocations are never served via the cache so just call allocateHuge
  54. allocateHuge(buf, reqCapacity);
  55. }
  56. }

PoolThreadCache类下的allocateTiny方法:

  1. boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) {
  2. return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity);
  3. }
  4. //从cache中获取buf
  5. private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
  6. int idx = PoolArena.tinyIdx(normCapacity);
  7. if (area.isDirect()) {
  8. return cache(tinySubPageDirectCaches, idx);
  9. }
  10. return cache(tinySubPageHeapCaches, idx);
  11. }

根据需要的容量获取对应的格子,走到PoolArena类下面的tinyIdx方法:

  1. static int tinyIdx(int normCapacity) {
  2. return normCapacity >>> 4;
  3. }

PoolThreadCache类下的allocate方法,把缓存格子的内存分配到buf

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

下面是具体跟踪代码的步骤图:



上面的源码是以tiny类型为例,其他两种类型类似,当第一次分配创建了一块新的内存,然后被成功回收到内存缓冲池后,再次分配对应大小的内存,会直接从内存缓冲池中取,不会再次分配一块新的内存了

内存回收的过程

接下来跟踪release()方法,看下内存回收的过程

  1. buf1.release();

第一次进入AbstractReferenceCountedByteBuf类:

Buf的引用计数器,用于内存复用,有一个计数器refCnt,retain()计数器加一,release()计数器减一,

直到计数器为0,才调用deallocate()释放,deallocate()方法由具体的buf自己实现。

  1. @Override
  2. public boolean release() {
  3. return release0(1);
  4. }
  1. private boolean release0(int decrement) {
  2. int rawCnt = nonVolatileRawCnt(), realCnt = toLiveRealCnt(rawCnt, decrement);
  3. //判断当前buf有没有被引用了,没有的话就调用deallocate
  4. if (decrement == realCnt) {
  5. if (refCntUpdater.compareAndSet(this, rawCnt, 1)) {
  6. deallocate();
  7. return true;
  8. }
  9. return retryRelease0(decrement);
  10. }
  11. return releaseNonFinal0(decrement, rawCnt, realCnt);
  12. }

进入PooledByteBuf类:

  1. @Override
  2. protected final void deallocate() {
  3. if (handle >= 0) {
  4. final long handle = this.handle;
  5. //表示当前的buf不在使用任何一块内存区域
  6. this.handle = -1;
  7. //设置memory为null
  8. memory = null;
  9. //释放buf的内存
  10. chunk.arena.free(chunk, tmpNioBuf, handle, maxLength, cache);
  11. tmpNioBuf = null;
  12. chunk = null;
  13. //把buf对象放入对象回收栈
  14. recycle();
  15. }
  16. }

再次进入PoolArena类:

  1. void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacity, PoolThreadCache cache) {
  2. //判断是否是unpooled
  3. if (chunk.unpooled) {
  4. int size = chunk.chunkSize();
  5. destroyChunk(chunk);
  6. activeBytesHuge.add(-size);
  7. deallocationsHuge.increment();
  8. } else {
  9. //判断是哪种类型,tiny、small、normal
  10. SizeClass sizeClass = sizeClass(normCapacity);
  11. //放入缓存
  12. if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) {
  13. // cached so not free it.
  14. return;
  15. }
  16. freeChunk(chunk, handle, sizeClass, nioBuffer);
  17. }
  18. }
  19. //计算内存区域是哪种类型
  20. private SizeClass sizeClass(int normCapacity) {
  21. if (!isTinyOrSmall(normCapacity)) {
  22. return SizeClass.Normal;
  23. }
  24. return isTiny(normCapacity) ? SizeClass.Tiny : SizeClass.Small;
  25. }

然后到PoolThreadCache类:

  1. boolean add(PoolArena<?> area, PoolChunk chunk, ByteBuffer nioBuffer,
  2. long handle, int normCapacity, SizeClass sizeClass) {
  3. MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass);
  4. if (cache == null) {
  5. return false;
  6. }
  7. //加入到缓存队列
  8. return cache.add(chunk, nioBuffer, handle);
  9. }
  10. private MemoryRegionCache<?> cache(PoolArena<?> area, int normCapacity, SizeClass sizeClass) {
  11. //判断是哪种类型,然后把内存回收到哪一块
  12. switch (sizeClass) {
  13. case Normal:
  14. return cacheForNormal(area, normCapacity);
  15. case Small:
  16. return cacheForSmall(area, normCapacity);
  17. case Tiny:
  18. return cacheForTiny(area, normCapacity);
  19. default:
  20. throw new Error();
  21. }
  22. }
  23. private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
  24. int idx = PoolArena.tinyIdx(normCapacity);
  25. if (area.isDirect()) {
  26. return cache(tinySubPageDirectCaches, idx);
  27. }
  28. return cache(tinySubPageHeapCaches, idx);
  29. }

上述跟踪代码步骤图:

ByteBuf零拷贝机制

Netty的零拷贝机制,是一种应用层的实现,和底层的JVM、操作系统内存机制没有过多的关联

几种示例

一:CompositeByteBuf,将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝

  1. public static void test1() {
  2. ByteBuf buf1 = Unpooled.buffer(4);
  3. ByteBuf buf2 = Unpooled.buffer(3);
  4. byte[] bytes1 = {1,2};
  5. byte[] bytes2 = {3,4,5};
  6. buf1.writeBytes(bytes1);
  7. buf2.writeBytes(bytes2);
  8. CompositeByteBuf byteBuf = Unpooled.compositeBuffer();
  9. byteBuf = byteBuf.addComponents(true, buf1, buf2);
  10. System.out.println("byteBuf: " + byteBuf.toString());
  11. }



上面输出结果,ridx是顺序读的读取位置,widx是顺序写的写入位置,cap是新的ByteBuf的容量,components是指新的ByteBuf是由几个ByteBuf组成



二:wrappedBuffer()方法,将byte[]数组包装成ByteBuf对象

  1. public static void test2() {
  2. byte[] bytes = {1,2,3,4,5};
  3. ByteBuf buf = Unpooled.wrappedBuffer(bytes);
  4. System.out.println("buf:" + buf.toString());
  5. }



输出结果中:ridx是顺序读的读取位置,widx是顺序写的写入位置,cap是ByteBuf的容量,新的ByteBuf里存的是数组的引用地址,实质操作的还是原来的数组



三:slice()方法,将一个ByteBuf对象切分成多个ByteBuf对象

  1. public static void test3() {
  2. ByteBuf buf = Unpooled.wrappedBuffer("hello".getBytes());
  3. ByteBuf byteBuf = buf.slice(1,2);
  4. System.out.println("byteBuf:" + byteBuf.toString());
  5. }



输出结果中,可以看到,有两个ByteBuf,其中一个是原有的,新的ByteBuf中存放了原来的ByteBuf的引用地址,另一个是分割后的ByteBuf的引用地址

结束语

到此,Netty的ByteBuf就结束了,下面会介绍Netty的启动以及解决TCP粘包/拆包问题

网络编程Netty入门:ByteBuf分析的更多相关文章

  1. Java网络编程 -- Netty入门

    Netty简介 Netty是一个高性能,高可扩展性的异步事件驱动的网络应用程序框架,它极大的简化了TCP和UDP客户端和服务器端网络开发.它是一个NIO框架,对Java NIO进行了良好的封装.作为一 ...

  2. 网络编程Netty入门:Netty简介及其特性

    目录 Netty的简介 Netty的特性 Netty的整体结构 Netty的核心组件 Netty的线程模型 结束语 Netty的简介 Netty是一个java开源框架,是基于NIO的高性能.高可扩展性 ...

  3. 网络编程Netty入门:EventLoopGroup分析

    目录 Netty线程模型 代码示例 NioEventLoopGroup初始化过程 NioEventLoopGroup启动过程 channel的初始化过程 Netty线程模型 Netty实现了React ...

  4. 网络编程Netty入门:Netty的启动过程分析

    目录 Netty的启动过程 Bootstrap 服务端的启动 客户端的启动 TCP粘包.拆包 图示 简单的例子 Netty编解码框架 Netty解码器 ByteToMessageDecoder实现类 ...

  5. 网络编程Netty入门:责任链模式介绍

    目录 责任链模式 责任链模式的简单实现 Netty中的ChannelPipeline责任链 服务端接收客户端连接 pipeline初始化 入站事件和出站事件 Pipeline中的Handler Pip ...

  6. Java网络编程 -- Netty中的ByteBuf

    由于JDK中提供的ByteBuffer无法动态扩容,并且API使用复杂等原因,Netty中提供了ByteBuf.Bytebuf的API操作更加便捷,可以动态扩容,提供了多种ByteBuf的实现,以及高 ...

  7. socket 网络编程高速入门(一)教你编写基于UDP/TCP的服务(client)通信

    由于UNIX和Win的socket大同小异,为了方便和大众化,这里先介绍Winsock编程. socket 网络编程的难点在入门的时候就是对基本函数的了解和使用,由于这些函数的结构往往比較复杂,參数大 ...

  8. 浅谈iOS网络编程之一入门

    计算机网络,基本上可以抽象是端的通信.实际在通讯中会用到不同的设备,不同的硬件中,为了能友好的传输信息,那么建立一套规范就十分必要了.先来了解一些基本概念 了解网络中传输的都是二进制数据流.  2.了 ...

  9. ios网络编程(入门级别)-- 基础知识

    在学习ios的过程中,停留在UI控件很长时间,现在正在逐步的接触当中!!!!!!在这个过程中,小编学到了一些关于网络编程知识,并且有感而发,在此分享一下: 关于网络请求的重要性我想不用多说了吧!!!对 ...

随机推荐

  1. winform解析json API数据

    {  "retCode" : "SUCCESS",  "msg" : "",  "data" : { ...

  2. 【原创】Linux虚拟化KVM-Qemu分析(十)之virtio驱动

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: KVM版本:5.9 ...

  3. 微信小程序:条件渲染wx:if和hidden

    一.条件渲染:wx:if,wx:elif,wx:else 花括号中的true或false可以改成变量从而来动态显示. 二.hidden 只显示hidden2 当标签不是频繁切换显示(控制是否渲染到页面 ...

  4. Apache支持Vue router使用 HTML5History 模式

    一.前言 前端Vue router 使用history模式,URL会比hash模式好看,这种模式要玩好,还需要后端配置支持,否则会报404错误. 注:1.前端代码省略. 2.此处后台使用Apache服 ...

  5. Dockerfile怎么用

    目录 Docker的Dockerfile 1.常用命令 2.使用Dockerfile创建镜像 Docker的Dockerfile Dockerfile是由一系列命令和参数构成的脚本文件,这些命令应用于 ...

  6. django 内置“信号”机制和自定义方法

    一.引子 在操作数据的时候,假设我们需要记录一些日志,这个时候,我们需要用什么来显示这个需求呢?装饰器?装饰器只能先实现整体的操作.在django 里面有这么一个东西--信号 下面我们就来了解了解它. ...

  7. c++指针数组与二维数组的最大区别

    下面随笔是关于指针数组说明及与二维数组的最大区别. 指针数组 数组的元素是指针型 例 利用指针数组存放矩阵 1 #include 2 using namespace std; 3 int main() ...

  8. Java 语言基础 02

    语言基础·二级 顺序结构语句 * A:什么是流程控制语句    * 流程控制语句:可以控制程序的执行流程. * B:流程控制语句的分类    * 顺序结构    * 选择结构    * 循环结构 *  ...

  9. There only 10 people use the same phone as you(i春秋CTF题解)

      (1)访问网址进行CTF测试,仅出现登陆与注册的页面 (2)进行注册尝试登陆并进行burp抓取数据包: (3)注册成功,进行登陆尝试查看信息是否具有提示,在登录的页面只有两个点击页面,一个为:Ch ...

  10. 基于CefSharp开发浏览器(九)浏览器历史记录弹窗面板

    一.前言 前两篇文章写的是关于浏览器收藏夹的内容,因为收藏夹的内容不会太多,故采用json格式的文本文件作为收藏夹的存储方式. 关于浏览器历史记录,我个人每天大概会打开百来次网页甚至更多,时间越长历史 ...