Netty's own ByteBuf
ByteBuffer is designed to solve the problems of ByteBuffer and meet the daily needs of network application developers.
Disadvantages of JDK ByteBuffer:
- Unable to expand dynamically: the length is fixed and cannot be expanded and contracted dynamically. When the data is larger than the ByteBuffer capacity, an index out of bounds exception will occur.
- API use is complex: when reading and writing, you need to call methods such as flip() and rewind() manually. When using these APIs, you need to be very careful, otherwise it is easy to make mistakes.
What enhancements did ByteBuf make:
- Convenience of API operation
- Dynamic expansion
- Multiple ByteBuf implementations
- Efficient zero copy mechanism
ByteBuf operation
Three important properties of ByteBuf: readerIndex read location, writerIndex write location, and capacity
Two pointer variables are provided to support sequential read and write operations, readerIndex and writerIndex
Definition of common methods |
---|
Random access index getByte |
read in sequence* |
write in sequence* |
Clear read discardReadBytes |
clear buffer |
Search operation |
Tagging and resetting |
Reference count and release |
discardable bytes | readable bytes | writable bytes |
---|---|---|
Read disposable area | Readable area | Write area |
0<= readerIndex | <= writerIndex | <= capacity |
ByteBuf dynamic expansion
capacity default: 256 bytes; maximum: integer.max? Value (2GB)
When the write * method is called, it is checked by abstractbytebuf.ensurewriteble0.
capacity calculation method: abstractbytebufalocator.calculatenewcapacity
According to the minimum requirement of new capacity, there are two calculation methods:
No more than 4M: starting from 64 bytes, double each time until the calculated newCapacity meets the minimum requirement of new capacity.
Example: the current size is 256, 250 has been written, and 10 bytes of data continue to be written. The minimum required capacity is 261, so the new capacity is 64 * 2 * 2 * 2 = 512
Over 4M: new capacity = minimum requirement for new capacity / 4M * 4M +4M
Example: the current size is 3M, 3M has been written, and 2M data continues to be written. The minimum required capacity is 5M, and the new capacity is 9M (cannot exceed the maximum).
Source of 4M: a fixed threshold abstractbytebufalocator.calculate'u threshold
Choose the right ByteBuf implementation
Understand the core: the division of three latitudes, eight specific implementations
In / out of pile | Pooling | Access mode | Concrete implementation class | Remarks |
---|---|---|---|---|
unpool | safe | UnpooledHeapByteBuf | Array implementation | |
heap reactor | unsafe | UnpooledUnsafeHeapByteBuf | Unsafe class direct operation memory | |
pool | safe | PooledHeapByteBuf | ~ | |
unsafe | PooledUnsafeHeapByteBuf | ~ | ||
unpool | safe | UnpooledDirectByteBuf | NIO DirectByteBuffer | |
Outside the direct stack | unsafe | UnpooledUnsafeDirectByteBuf | ~ | |
pool | safe | PooledDirectByteBuf | ~ | |
unsafe | PooledUnsafeDirectByteBuf | ~ |
In use, all applications are made through the bytebufalocator allocator, which has the function of memory management
Implementation of Unsafe
Unsafe means unsafe operation. But the lower level operation will bring performance improvement and special functions. Netsafe will be used as much as possible in Netty.
The important feature of Java language is "write once and run everywhere", so it makes a lot of encapsulation for the underlying memory or other operations.
unsafe provides a series of methods for us to operate on the bottom layer, which may lead to incompatible or unknown exceptions.
Info. Returns only some low-level memory information | Objects. Provides methods for manipulating objects and their fields | Classes. Provides methods for manipulating classes and their static fields |
---|---|---|
addressSize | allocateInstance | staticFieldOffset |
pageSize | objectFieldOffset | defineClass |
defineAnonymousClass | ||
ensureClassInitialized |
Synchronization. Low level synchronization primitives | Memory. Direct access to memory method | Arrays. Operation array |
---|---|---|
monitorEnter | allocateMemory | arrayBaseOffset |
tryMonitorEnter | copyMemory | arrayIndexScale |
monitorExit | freeMemory | |
compareAndSwapInt | getAddress | |
putOrderedInt | getInt | |
putInt |
PooledByteBuf object, memory reuse
PoolThreadCache: a thread variable maintained by the pooledbybufalocator instance.
The MemoryRegionCache array of various classifications is used as memory cache. Inside the MemoryRegionCache is a linked list, and the Chunk is stored in the queue.
Memory reference is maintained in PoolChunk. The way of memory reuse is to point buf memory to chunk memory.
Pooledbytebufalocator.iobuffer operation process sorting:
EventLoop - thread -- allocate -- > Arena (responsible for buf allocation management) - >
-
Create or reuse ByteBuf objects
PooledByteBuf stack RECYCLER ----> buffer cache -
Try to reuse memory space from the corresponding cache
PoolThreadCache TINY_MR_CACHE * 32 Q[512] SMALL_MR_CACHE * 4 Q[256] NORMAL_MR_CACHE * 3 Q[64] -
When there is no cache, apply for unpool directly from memory
Zero copy mechanism
Netty's zero copy mechanism is an application layer implementation. There is not much relationship with the underlying JVM and operating system memory mechanism.
-
CompositeByteBuf, which merges multiple bytebufs into a logical ByteBuf, avoiding the copy between each ByteBuf.
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); ByteBuf newBuffer = compositeByteBuf.addComponents(true, buffer1, buffer2);
-
wrappedBuffer() method, which wraps the byte [] array as a ByteBuf object.
ByteBuf newBuffer = Unpooled.wrappedBuffer(new byte[]{1, 2, 3, 4, 5});
-
slice() method. A ByteBuf object is divided into multiple ByteBuf objects.
ByteBuf buffer1 = Unpooled.wrappedBuffer("hello".getBytes()); ByteBuf newBuffer = buffer1.slice(1, 2);
ByteBuf test code
import io.netty.buffer.ByteBuf; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import java.util.Arrays; /** * @Author: Wenx * @Description: * @Date: Created in 2019/11/25 22:31 * @Modified By: */ public class ByteBufDemo { public static void main(String[] args) { apiTest(); compositeTest(); wrapTest(); sliceTest(); } public static void apiTest() { // +-------------------+------------------+------------------+ // | discardable bytes | readable bytes | writable bytes | // | | (CONTENT) | | // +-------------------+------------------+------------------+ // | | | | // 0 <= readerIndex <= writerIndex <= capacity // 1. Create a non pooled ByteBuf with a size of 10 bytes ByteBuf buf = Unpooled.buffer(10); //ByteBuf buf = Unpooled.directBuffer(10); println("1.Original ByteBuf by", buf); // 2. Write a piece of content byte[] bytes = {1, 2, 3, 4, 5}; buf.writeBytes(bytes); print("2.Write in bytes by", bytes); println("After writing content ByteBuf by", buf); // 3. Read a paragraph byte b1 = buf.readByte(); byte b2 = buf.readByte(); print("3.Read out bytes by", new byte[]{b1, b2}); println("After reading ByteBuf by", buf); // 4. Discard the read content buf.discardReadBytes(); println("4.After discarding the read content ByteBuf by", buf); // 5. Clear read / write pointer buf.clear(); println("5.After clearing the read / write pointer ByteBuf by", buf); // 6. Write a paragraph of content again, less than the first paragraph byte[] bytes2 = {1, 2, 3}; buf.writeBytes(bytes2); print("6.Write in bytes by", bytes2); println("After writing content ByteBuf by", buf); // 7. Reset ByteBuf buf.setZero(0, buf.capacity()); println("7.After clearing the content ByteBuf by", buf); // 8. Write a piece of content exceeding the capacity again byte[] bytes3 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; buf.writeBytes(bytes3); print("8.Write in bytes by", bytes3); println("After writing content ByteBuf by", buf); // Random access index getByte // read in sequence* // write in sequence* // Clear read discardReadBytes // clear buffer // Search operation // Tagging and resetting // Full code example: Reference // The search operation reads the specified location buf.getByte(1); } public static void compositeTest() { ByteBuf buffer1 = Unpooled.buffer(3); buffer1.writeByte(1); ByteBuf buffer2 = Unpooled.buffer(3); buffer2.writeByte(4); print("buffer1 by", buffer1); print("buffer2 by", buffer2); CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); ByteBuf newBuffer = compositeByteBuf.addComponents(true, buffer1, buffer2); println("CompositeByteBuf by", newBuffer); } public static void wrapTest() { byte[] arr = {1, 2, 3, 4, 5}; ByteBuf newBuffer = Unpooled.wrappedBuffer(arr); print("byte[]by", arr); print("wrappedBuffer by", newBuffer); print("newBuffer.getByte(4)by", newBuffer.getByte(4)); arr[4] = 6; println("byte[4] = 6; after newBuffer.getByte(4)by", newBuffer.getByte(4)); } public static void sliceTest() { ByteBuf oldBuffer = Unpooled.wrappedBuffer("hello".getBytes()); ByteBuf newBuffer = oldBuffer.slice(1, 2); print("oldBuffer by", oldBuffer); print("oldBuffer.slice(1, 2); by", newBuffer); print("newBuffer.getByte(0)by", newBuffer.getByte(0)); print("newBuffer.getByte(1)by", newBuffer.getByte(1)); // Reference of original buf in new buf ByteBuf buf = newBuffer.unwrap(); print("newBuffer.unwrap()by", buf); print("buf.getByte(0)by", buf.getByte(0)); print("buf.getByte(1)by", buf.getByte(1)); print("buf.getByte(2)by", buf.getByte(2)); print("buf.getByte(3)by", buf.getByte(3)); print("buf.getByte(4)by", buf.getByte(4)); } private static void print(String str, byte b) { System.out.println(String.format("%s==========>%s", str, b)); } private static void print(String str, byte[] bytes) { System.out.println(String.format("%s==========>%s", str, Arrays.toString(bytes))); } private static void print(String str, ByteBuf buf) { print(str, buf, ""); } private static void print(String before, ByteBuf buf, String after) { byte[] bytes; if (buf.hasArray()) { bytes = buf.array(); } else { int capacity = buf.capacity(); bytes = new byte[capacity]; for (int i = 0; i < buf.capacity(); i++) { bytes[i] = buf.getByte(i); } } System.out.println(String.format("%s==========>%s(ridx:%s, widx: %s, cap: %s)%s", before, Arrays.toString(bytes), buf.readerIndex(), buf.writerIndex(), buf.capacity(), after)); } private static void println(String str, byte b) { System.out.println(String.format("%s==========>%s\n", str, b)); } private static void println(String str, ByteBuf buf) { print(str, buf, "\n"); } }