Java learning notes 16 netty buffer ByteBuf details

Keywords: Programming Netty Java network JDK

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) - >

  1. Create or reuse ByteBuf objects

    PooledByteBuf
    stack
    RECYCLER ----> buffer
    cache
  2. 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]
  3. 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.

  1. 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);
    
  2. wrappedBuffer() method, which wraps the byte [] array as a ByteBuf object.

    ByteBuf newBuffer = Unpooled.wrappedBuffer(new byte[]{1, 2, 3, 4, 5});
    
  3. 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");
    }
}

Posted by kmutz22 on Tue, 26 Nov 2019 08:58:04 -0800