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