As the data container of Netty, network communication involves the movement of byte sequences. Efficient and easy-to-use data structures are essential. Java NIO replaced ByteBuffer, mainly because ByteBuf is more efficient and easy to use, automatic capacity expansion and other functions that ByteBuffer does not have
1. ByteBuf data structure
ByteBuf features:
-
Created from the utility class Unpooled
-
Indexed random access
-
Indexed sequential access
-
Search operations
-
Index Mark and Reset
-
Derived buffer
-
ByteBuf and JDK ByteBuffer can be converted flexibly
In AbstractByteBuf implementations, two variables are maintained:
- ReaderIndex – The current index position of the read
- WriterIndex – The current index position of the write
ByteBuf methods whose names start with read and write will advance the corresponding index. Those that start with get and set do not. ByteBuf Default true maximum capacity is integer.max_value
ReadXXX and writeXXX advance readerIndex and writeIndex, respectively.
2. ByteBuf operation
2.1 Random access index
Random access to ByteBuf is provided through the getBytes series of interfaces.
ByteBuf buffer = Unpooled.buffer(3.3);
buffer.writeBoolean(true);
buffer.writeBoolean(true);
buffer.getBytes(0.new byte[1]);
System.out.println(buffer.readerIndex());
Copy the code
Random access with getBytes does not change readerIndex
2.2 Sequential Index Access
Get read and write indexes from readerIndex() and writerIndex().
2.3 Discarded bytes
Segments marked as throwable bytes contain bytes that have already been read. They can be discarded and reclaimed by calling the discardReadBytes() method. This segment has an initial size of 0, is stored in readerIndex and increases as read operations are performed (get operations do not move the readerIndex). But this discarding does not mean discarding the bytes of the field that has been read, but rather moving the number of bytes that have not been read to the beginning. (Doing so may cause memory replication)
2.4 Readable bytes and writable sections
2.5 Read and write Index Management
Mark and reset ByteBuf’s readerIndex and by calling markReaderIndex(), markWriterIndex(), resetWriterIndex(), and resetReaderIndex() WriterIndex.
Indexes can also be moved to a specified location by calling readerIndex(int) or writerIndex(int). Trying to set any an index to an invalid position will lead to a IndexOutOfBoundsException.
You can set both readerIndex and writerIndex to 0 by calling the clear() method. Note that this does not clear the contents of memory. (Content still exists but not all data is wiped)
3. ByteBuf implementation source code analysis
The implementation of ByteBuf can be viewed at three latitudes:
- Pooled and Unpooled
- Pooled: Each memory request takes a contiguous segment from the pre-allocated space and puts it back when it is used. Similar to thread pool operations
- Unpooled: New memory is requested each time
- The Unsafe and Unsafe
- Unsafe: Memory is manipulated directly through the Unsafe class, and memory management is left to the developer
- Non-unsafe: Memory is handled through JDK apis, meaning that memory management is managed by the JVM
- Heap and Direct
- Heap: the Heap memory of the JVM
- Direct: Out-of-heap memory is also called Direct memory. The Unsafe API is used to allocate physical memory, which is not in the JVM heap and therefore must be freed manually by users
ByteBuf has eight major implementations.
3.1 Pooled and unpooled ByteBuf
Pooled memory is mainly managed by Netty and is overwritten and returned to the memory pool after being used. Instead, unpooled memory is created and freed each time it is used.
3.2 ByteBufAllocator
The ByteBufAllocator interface is the top-level interface for allocating ByteBuf memory. It is responsible for allocating all types of ByteBuf memory and requires thread-safe implementation.
ByteBufAllocator Important methods:
// Whether the heap is in or out depends on the implementation
ByteBuf buffer(a);
ByteBuf buffer(int initialCapacity);
ByteBuf buffer(int initialCapacity, int maxCapacity);
// Out-of-heap memory allocation ByteBuf
ByteBuf directBuffer(a);
ByteBuf directBuffer(int initialCapacity);
ByteBuf directBuffer(int initialCapacity, int maxCapacity);
// Heap memory allocation ByteVBuf
ByteBuf heapBuffer(a);
ByteBuf heapBuffer(int initialCapacity);
ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
Copy the code
Let’s look at the source code to see what rules are used to allocate, what is the strategy? Look at the AbstractByteBufAllocator
public abstract class AbstractByteBufAllocator implements ByteBufAllocator {
protected AbstractByteBufAllocator(a) {
this(false); // Heap allocation is used by default
}
// Use preferDirect to determine whether to use out-of-heap memory allocation
protected AbstractByteBufAllocator(boolean preferDirect) {
directByDefault = preferDirect && PlatformDependent.hasUnsafe(); // Whether to allocate memory in Unsafe mode
emptyBuf = new EmptyByteBuf(this);
}
// Omit some code
}
Copy the code
The “in” or “out” assignment of ByteBuf depends on whether the preferDirect constructor parameter is true or false, in which case the platform relies on Unsafe.
// Concrete class implementation
protected abstract ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity);
// Concrete class implementation
protected abstract ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity);
Copy the code
AbstractByteBufAllocator#newHeapBuffer and AbstractByteBufAllocator#newDirectBuffer are abstract methods whose execution is implementation-dependent. There are two classes implemented
- PooledByteBufAllocator
- UnpooledByteBufAllocator
These two implementations distinguish between pooled and unpooled. The decision whether to use Unsafe or not is made automatically.
3.3 Unpooled Memory allocation
UnpooledByteBufAllocator implements the non-pooled ByteBuf allocation policy. There are two specific implementations:
- Heap memory allocation
- Off-heap memory allocation
3.3.1 Unpooled heap memory allocation
UnpooledByteBufAllocator#newHeapBuffer allocates memory in the heap:
@Override
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
return PlatformDependent.hasUnsafe() ?
new InstrumentedUnpooledUnsafeHeapByteBuf(this, initialCapacity, maxCapacity) :
new InstrumentedUnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
}
Copy the code
Through PlatformDependent. HasUnsafe judge whether the operating system supports the Unsafe (final judgment method invocation) :
private static Throwable unsafeUnavailabilityCause0(a) {
if (isAndroid()) {
logger.debug("sun.misc.Unsafe: unavailable (Android)");
return new UnsupportedOperationException("sun.misc.Unsafe: unavailable (Android)");
}
if (isIkvmDotNet()) {
logger.debug("sun.misc.Unsafe: unavailable (IKVM.NET)");
return new UnsupportedOperationException("sun.misc.Unsafe: unavailable (IKVM.NET)");
}
Throwable cause = PlatformDependent0.getUnsafeUnavailabilityCause();
if(cause ! =null) {
return cause;
}
try {
boolean hasUnsafe = PlatformDependent0.hasUnsafe();
logger.debug("sun.misc.Unsafe: {}", hasUnsafe ? "available" : "unavailable");
return hasUnsafe ? null : PlatformDependent0.getUnsafeUnavailabilityCause();
} catch (Throwable t) {
logger.trace("Could not determine if Unsafe is available", t);
// Probably failed to initialize PlatformDependent0.
return new UnsupportedOperationException("Could not determine if Unsafe is available", t); }}Copy the code
-
Create InstrumentedUnpooledUnsafeHeapByteBuf support the Unsafe
InstrumentedUnpooledUnsafeHeapByteBuf is invoked UnpooledUnsafeHeapByteBuf# allocateArray method:
// This class is the inner class of UnpooledByteBufAllocator private static final class InstrumentedUnpooledUnsafeHeapByteBuf extends UnpooledUnsafeHeapByteBuf { // Omit some code @Override protected byte[] allocateArray(int initialCapacity) { // Allocate memory byte[] bytes = super.allocateArray(initialCapacity); ((UnpooledByteBufAllocator) alloc()).incrementHeap(bytes.length); returnbytes; }}public class UnpooledUnsafeHeapByteBuf extends UnpooledHeapByteBuf { @Override protected byte[] allocateArray(int initialCapacity) { // Allocate memory returnPlatformDependent.allocateUninitializedArray(initialCapacity); }}Copy the code
Finally through PlatformDependent# allocateUninitializedArray methods to allocate memory
public static byte[] allocateUninitializedArray(int size) { return UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD < 0 || UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD > size ? new byte[size] : PlatformDependent0.allocateUninitializedArray(size); } Copy the code
-
Create InstrumentedUnpooledHeapByteBuf does not support the Unsafe
InstrumentedUnpooledHeapByteBuf is invoked UnpooledHeapByteBuf# allocateArray method:
protected byte[] allocateArray(int initialCapacity) { return new byte[initialCapacity]; } Copy the code
Allocate memory by creating byte arrays directly in new mode
Description:
- With support for broadening to allocate memory in the heap, all read and write operations depend on Unsafe.
- Unsafe is not supported. Byte numbers are created using new byte[initialCapacity], read and write are created using indexes, and memory release is dependent on the JVM
3.3.1 Unpooled out-of-heap memory allocation
UnpooledByteBufAllocator#newDirectBuffer allocates out-of-heap memory:
@Override
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);
}
Copy the code
Out-of-pool memory allocation also determines whether the operating system supports Unsafe
-
Support the Unsafe
- Create InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf noCleaner to true,
- Create InstrumentedUnpooledUnsafeDirectByteBuf noCleaner to false,
-
Does not support the Unsafe
Create InstrumentedUnpooledDirectByteBuf
Call for InstrumentedUnpooledDirectByteBuf UnpooledDirectByteBuf# allocateDirect method, The bottom layer is created by JavaNIO’s ByteBuffer#allocateDirect, and finally returned by the new DirectByteBuffer(capacity) object.
3.4 Pooling Memory Allocation
Pooled memory is allocated by PooledByteBufAllocator. For details, see PooledByteBufAllocator.
4. To summarize
- Netty’s powerful data container, ByteBuf, not only solves the pitfalls of ByteBuffer in JDK NIO, but also provides a more user-friendly interface. It can also be converted to ByteBuffer
- ByteBuf uses different Pointers for reading and writing, and the read/write mode can be switched at will
- Capacity can be dynamically expanded on demand, and pooling is supported