Java NIO(2) ByteBuffer class source code analysis

ByteBuffer can be used to buffer bytes by looking at its name. Let's first look at the usage of ByteBuffer.
    public static void main(String[] args) {
        // Request 1024 bytes
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // Write 123456 to byteBuffer
        byteBuffer.put("123456".getBytes());
        // Switch to read mode
        byteBuffer.flip();
        // Whether there are unread bytes in buffer
        while (byteBuffer.hasRemaining()) {
            // Read a byte
            System.out.print((char)byteBuffer.get());
        }
    }
Let's first look at the definition of ByteBuffer class
    public abstract class ByteBuffer extends Buffer
        implements Comparable<ByteBuffer> {
        final byte[] hb; // Buffer array, HeapByteBuffer only                 
        final int offset; // Offset, where to start reading and writing, generally 0
        boolean isReadOnly;  
    }
From the definition of ByteBuffer, we can see that it is an abstract class, so we can not directly initialize ByteBuffer. It is initialized by the allocate() method or allocateDirect().
At the same time, we know that ByteBuffer inherits the Buffer class
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
According to the above code, the initialization of ByteBuffer is through the initialization of HeapByteBuffer or DirectByteBuffer. Generally, HeapByteBuffer is commonly used, so let's take a look at the HeapByteBuffer class, and DirectByteBuffer will be analyzed later.
    class HeapByteBuffer extends ByteBuffer
According to the definition of HeapByteBuffer, it inherits the ByteBuffer class. Let's see how it's initialized
    // allocate uses this constructor to initialize the array hb directly by new byte[cap]
    HeapByteBuffer(int cap, int lim) {          
        super(-1, 0, lim, cap, new byte[cap], 0);//offset = 0 
    }

    HeapByteBuffer(byte[] buf, int off, int len) {
        super(-1, off, off + len, buf.length, buf, 0);
    }
    // It is initialized by calling the constructor of the parent class ByteBuffer 
    ByteBuffer(int mark, int pos, int lim, int cap,  
                 byte[] hb, int offset)
    {
        //Call the constructor of Buffer, initialize mark, pos,lim,cap
        super(mark, pos, lim, cap); 
        this.hb = hb;
        this.offset = offset;
    }
    ByteBuffer(int mark, int pos, int lim, int cap) { 
        this(mark, pos, lim, cap, null, 0);
    }
Let's take a look at its common methods 
get method
    // Get the content of hb[position+offset+1]
    public byte get() {
        return hb[ix(nextGetIndex())];
    }
    // Get index of next location
    protected int ix(int i) {
        return i + offset;
    }
    // Defined in the parent Buffer of ByteBuffer class to get the next position of position
    final int nextGetIndex() {                           
        if (position >= limit)
            throw new BufferUnderflowException();
        return position++;
    }
put method
    //Write a byte to the position of byte[position + +] 
    public ByteBuffer put(byte x) {
        hb[ix(nextPutIndex())] = x;
        return this;
    } 
Other methods can refer to the previous Buffer source code analysis. Finally, let's look at the DirectByteBuffer class, which is not allocated directly from the JVM heap, but directly requests memory from the operating system through local methods.
    DirectByteBuffer(int cap) {               
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);
        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }
It requests size memory directly through unsafe.allocateMemory(size). After allocating out of heap memory, the allocated out of heap memory base address will be returned and assigned to the address property. In this way, when we use JNI to operate the out of heap memory, we use this address.

Tags: jvm

Posted on Fri, 01 May 2020 03:33:25 -0700 by ILYAS415