Java NIO basics and javanio Basics

Source: Internet
Author: User
Tags rewind

Java NIO basics and javanio Basics

Java introduced the java. nio class library in JDK1.4, opening the door for Java to enter the backend Server and middleware development.

In general, nio represents New I/O, but in essence, we can understand it as NonBlocking I/O (non-blocking ).

The core content of java. nio is Buffer, Channel (SelectableChannel), and Selector. Close cooperation between the three is the key to implementing asynchronous non-blocking Server development. Any Java programmer who wants to develop Java backend servers and middleware should have an in-depth understanding of Java NIO. The following sections describe each other.

1. java. nio. Buffer

1.1 essence of Buffer

Buffer is essentially a container (array implementation ). After initialization, the size is fixed. The underlying layer uses arrays to store original types of containers (byte, char, short, int, long, float, and double ). Each primitive type (except boolean) corresponds to a subclass, But we generally use ByteBuffer, because the OS network layer finally transmits byte:

That is to say, a non-blocking network program is compiled using java. nio. ByteBuffer is generally used for sending and receiving network bytes through a Channel, and is temporarily stored as a container (array.

1.2 core attributes of Buffer:

    // Invariants: mark <= position <= limit <= capacity    private int mark = -1;    private int position = 0;    private int limit;    private int capacity;

Capacity:Describe the size of the underlying array, which remains unchanged after initialization. There is nothing to say. The most important is position and limit.

They all describe the index inside an array, that is, a position of an array or an array element.

Position:The value is the position of the next array element to be written/read; position increases with the occurrence of easy read/write operations;

Limit: the maximum limit between the write and read operations of an array element. That is, the first position of the array element that cannot be read/written;

The existence of position is well understood. Why do we need a limit attribute?

This is because Buffer can both be written and read. For example, what should I do if I first write eight bytes to the Buffer and then I want to read these eight bytes?

The first step is to change the position from 8 to 0, because we need to start reading from 0;

Step 2: read from 0. What is the position of the read array? This is the function implemented by limit. We use limit = 8 to describe the maximum limit of read operations.

This is the function implemented by the Buffer. flip () function:

    public final Buffer flip() {        limit = position;        position = 0;        mark = -1;        return this;    }

We can see that after 8 bytes are written, position = 8, and then we call the flip () function, then limit = 8, position = 0; so the subsequent read operations, read from 0 to 8.

Therefore, the conversion from Buffer writing to Buffer reading is realized. (You may be familiar with Netty. Netty implements a ByteBuf class to simplify java. nio. ByteBuffer)

Mark:This attribute is used for implementation. For the moment, remember the current position. Then I perform some read/write operations (the position will change). After the operation is complete, I call the reset () function, the positon can be changed back to the position remembered above.

    public final Buffer mark() {        mark = position;        return this;    }    public final Buffer reset() {        int m = mark;        if (m < 0)            throw new InvalidMarkException();        position = m;        return this;    }

SoMark attribute, mark () function, and reset () function. They are a group and used together..

1.3 differentiate the three functions of Buffer:

Clear () function, flip () function, rewind () function:

    public final Buffer flip() {        limit = position;        position = 0;        mark = -1;        return this;    }    public final Buffer rewind() {        position = 0;        mark = -1;        return this;    }    public final Buffer clear() {        position = 0;        limit = capacity;        mark = -1;        return this;    }

Both position = 0, but limit = postion; limit = capacity is set for both flip () and clear (), but limit is not modified for rewind.

Flip () is used to implement Buffer read/write conversion. Both rewind () and clear () Implement Buffer read/write Starting from 0, but one modifies the limit, and the other does not.

1.4 rewind () and compact ()

    public ByteBuffer compact() {        System.arraycopy(hb, ix(position()), hb, ix(0), remaining());        position(remaining());        limit(capacity());        discardMark();        return this;    }

The compact () function implements the following: for example, if I read a Buffer but have not read it, and then start writing again, We can discard the read, release the occupied space and move the unread to the array position starting from 0 in the Buffer, that is, System. arraycopy (hb, ix (position (), hb, ix (0), remaing (); implements the copy function.

Remaiing () returns the number of remaining bytes. Position (remaining (), and limit (capacity;

Obviously, this is done to have a larger space to accommodate the next read. Because the system discards the read content and releases the space it occupies.

1.5 ByteBuffer read (get) and write (put)

The read and write operations of ByteBuffer can be dividedRelative and absoluteAnd can be dividedSingle-byte, batch read/write;

Get ()The function is to read a single byte at the relative position. It first reads the byte at the position, and then the position ++;

Put (byte B)A function writes a single byte at a relative position. It performs write operations first and then position ++;

Get (int index)Is to read the single byte at the absolute position index, position unchanged;

Put (int index, byte B)It is the write operation of the absolute position index, but the position remains unchanged;

Get (byte [] dst, int offset, int length)It is a batch read operation. Note that bytes in ByteBuffer are read to dst instead of Bytebuffer;

    public ByteBuffer get(byte[] dst, int offset, int length) {        checkBounds(offset, length, dst.length);        if (length > remaining())            throw new BufferUnderflowException();        int end = offset + length;        for (int i = offset; i < end; i++)            dst[i] = get();        return this;    }

We can see that the length parameter cannot be greater than remaining (), that is, the length cannot be greater than the remaining bytes in ByteBuffer. Therefore, it is generally used as follows:

Int len = buffer. remaining ();

Buffer. get (dst, 0, len );

Get (byte [] dst) is equivalent to: get (dst, 0, dst. length)

Compared with batch writing, there is also a corresponding batch read:

Put (byte [] src, int offset, int length)

The length cannot exceed the remaining bytes of the buffer;

Put (byte [] src)It is equivalent to put (src, 0, src. length)

For writing, there is also a put function that implements read and write between two ByteBuffer:

    public ByteBuffer put(ByteBuffer src) {        if (src == this)            throw new IllegalArgumentException();        if (isReadOnly())            throw new ReadOnlyBufferException();        int n = src.remaining();        if (n > remaining())            throw new BufferOverflowException();        for (int i = 0; i < n; i++)            put(src.get());        return this;    }

As you can see, the remaining bytes of the source ByteBuffer cannot exceed the remaining space of the target ByteBuffer, otherwise it will overflow.

1.6 ByteBuffer allocation (storage space initialization), in-heap and out-of-heap memory

ByteBuffer. allocate (1024 );
ByteBuffer. allocatedirect( 1024 );

One is to allocate memory space on the JVM heap, and the other is to use the memory space outside the JVM heap:

    public static ByteBuffer allocate(int capacity) {        if (capacity < 0)            throw new IllegalArgumentException();        return new HeapByteBuffer(capacity, capacity);    }
    HeapByteBuffer(int cap, int lim) {        super(-1, 0, lim, cap, new byte[cap], 0);        /*        hb = new byte[cap];        offset = 0;        */    }

We can see that HeapByteBuffer is used, while the latter is directly new byte [cap]; it is obviously the memory allocated on the JVM stack;

Direct Allocation:

    public static ByteBuffer allocateDirect(int capacity) {        return new DirectByteBuffer(capacity);    }
DirectByteBuffer(int cap) { // package-private 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; }

Determine the alignment, calculate the correct size, and then call unsafe. allocateMemory (size); allocates out-of-heap memory and calls unsafe. setMemory (base, size, (byte) 0) clears the memory by 0.

And cleaner = Cleaner. create (this, new Deallocator (base, size, cap ));

Through a list, Cleaner maintains a Runnable object for recycling off-heap memory for each directBuffer ):

    private static class Deallocator implements Runnable    {        private static Unsafe unsafe = Unsafe.getUnsafe();        private long address;        private long size;        private int capacity;        private Deallocator(long address, long size, int capacity) {            assert (address != 0);            this.address = address;            this.size = size;            this.capacity = capacity;        }        public void run() {            if (address == 0) {                // Paranoia                return;            }            unsafe.freeMemory(address);            address = 0;            Bits.unreserveMemory(size, capacity);        }    }

The recycle operation occurs in the clean () method of Cleaner.

Generally, the allocate () function runs very fast because the JVM heap memory has been allocated from the OS, and the directAllocate () allocation is slower because it needs to be directly allocated from the OS; however, the former is the JVM heap memory and will be affected by GC, while the latter is the off-heap memory, which does not accept GC. Therefore, the latter is suitable for: allocated in advance, the memory will be reused to meet the needs of large-scale memory management, that is, the scenario of large-scale Server programs or middleware.

The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an
application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that
are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a
measureable gain in program performance.

In Mycat middleware, the allocated off-heap memory is used to process network bytes.

In addition, Bytebuffer. hasRemaining (); and Bytebuffer. remaining (); the function can determine whether space is writable, whether bytes are readable, and the amount of space.

2. Channel (SelectableChannel)

The second important component for Java to implement non-blocking multiplexing network programming is SelectableChannel, which means that the channel can be registered to the Selector object to achieve multiplexing. The most commonly used network servers are SocketChannel, ServerSocketChannel, and DatagramChannel (their corresponding blocked network programming classes are Socket, ServerSocket, and DatagramSocket ):

ByteBuffer is only a byte container, and bytes are sent and received through a channel. The role of Selector is to notify all channels registered on it of readiness..

Obviously, SocketChannel and ServerSocketChannel are used for the client and server of the TCP protocol respectively, while the DatagramChannel is used for the udp protocol.

2.1 SocketChannel

public abstract class SocketChannel extends AbstractSelectableChannel    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel

SocketChannel extends implements actselectablechannel, so it can be registered to Selector; ByteChannel interface is readable and writable, that is, "duplex ";

SocketChannel (ServerSocketChannel) differs from Socket (ServerSocket) in two aspects:SocketChannel can be registered on Selector; SocketChannel can be configured as non-blocking. With these two points, you can write non-blocking multiplexing network programs,Avoid the programming method of opening a thread for each connection in the past, save the overhead for a large number of thread context switches and thread-related overhead..

1) block-related APIs:

public final boolean isBlocking()public final Object blockingLock()

IsBlocking () is used to test whether the SocketChannel is blocked. blockingLock () is used to prevent modifications after configureBlocking () is called. After blockingLock () is called, only the thread holding the returned value can modify the blocking status of the SocketChannel.

2) connect related APIs:

boolean connect(SocketAddress remote)  boolean isConnected()  boolean isConnectionPending()  boolean finishConnect()  

The connect () function starts to initiate a link;

Whether isConnected () has been linked;

IsConnectionPending () connection Regular Expression in progress;

FinishConnect () client requests to complete the TCP connection process;

3) how to establish a connection to the Server

About the connect () method:

If this channel is in non-blocking mode then an invocation of this method initiates a non-blocking connection operation. if the connection is established immediately, as can happen with a local connection, then this method returnsTrue.Otherwise this method returnsFalseAnd the connection operation must later be completed by invokingfinishConnectMethod.

If it is in non-blocking mode and the connection establishment is completed immediately, the connect () method returns true; otherwise, connect () returns false, indicating that the connection cannot be completed immediately, and you need to call finishConnect () later () method to complete the connection establishment process!

So when and where will finishConnect () be called later?

The following describes the connection process:

SocketChannel channel = SocketChannel. open (); // obtain an instance

Channel. configureBlocking (false); // configure non-blocking

Channel. register (selector, SelectionKey. OP_CONNECT, att); // register with selector. The following event is: SelectionKey. OP_CONNECT.

Channel. connect (new InetSocketAddress ("192.168.1.3", 3306); // initiate a connection

Then selector calls select () to re-obtain the ready status of the channel registered on it, and then traverses its key set:

Set <SelectionKey> keys = selector. selectedKeys ();

for (SelectionKey key : keys) {if (key.isValid() && key.isConnectable()) {

If (channel. isConnectionPending ())
Try {

Channel. finishConnect ();

} Catch (Exception e ){

//....

}

//...

    } else {  key.cancel();  }
   }}

In this way, the establishment of the connect connection can be completed.

Make sure that:SelectionKey. isConnectable ()

    public final boolean isConnectable() {        return (readyOps() & OP_CONNECT) != 0;    }
SelectionKey. OP_CONNECT = 8

Suppose that a selection key's interest setContainsOP_CONNECTAt the start of a selection operation.If the selector detects that the corresponding socket channel is ready to complete its connection sequence, or has an error pending, then it will addOP_CONNECTTo the key's ready set and add the key to its selected-key set.

At the start of the connection, if intereset contains OP_CONNECT, then if selector. if the select () call finds that the channel is about to be completed or an error pending occurs, OP_CONNECT will be added to ready set (readyOps () return value. so we can call it like this.

FinishConnect () method:

Finishes the process of connecting a socket channel.

A non-blocking connection operation is initiated by placing a socket channel in non-blocking mode and then invoking itsconnectMethod.Once the connection is established, or the attempt has failed, the socket channel will become connectable and this method may be invoked to complete the connection sequence. If the connection operation failed then invoking this method will cause an appropriatejava.io.IOExceptionTo be thrown.

If this channel is already connected then this method will not block and will immediately returnTrue. If this channel is in non-blocking mode then this method will returnFalseIf the connection process is not yet complete.

If no connection is complete, false is returned. The next re-traversal key set after selector. select () will call the finishConnect () method again ....

Channel objects can be stored in att objects. Then the att object is obtained through the key.

2.2 ServerSocketChannel

Non-blocking processing on the server:

selector = Selector.open();serverChannel = ServerSocketChannel.open();serverChannel.configureBlocking(false);
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);serverChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024 * 16 * 2);serverChannel.bind(new InetSocketAddress(bindIp, port), 100);serverChannel.register(selector, SelectionKey.OP_ACCEPT);

The serverChannel registers with selector and focuses on the OP_ACCEPT event, that is, when a client SocketChannel is connected to the event.

selector.select(1000L);Set<SelectionKey> keys = selector.selectedKeys();try {for (SelectionKey key : keys) {if (key.isValid() && key.isAcceptable()) {

Channel = serverChannel. accept ();
Channel. configureBlocking (false );

Channel. register ()....

//... The connection process is complete.

} else {key.cancel();}}} finally {    keys.clear();}

ServerChannel is a process of continuous loop in a thread:

If a client SocketChannel is connected, accept () returns a channel, and then uses the socketChannel of the channel and client to complete subsequent network byte reading and sending operations.

3. Selector

The key for Selector to achieve IO multiplexing. Selector manages all the SocketChannel/serversocketchannels registered to the selector. It provides the ready notification service for them. Every time I/O of any channel is actually ready, selector. select () updates the key set, and returns the result in the congestion. Then we can traverse the key set to process the IO event.

The SelectionKey object works closely with Selector.

 

3.1 selector related APIs:

Selector.open()selector.select()selector.close()selector.keys()selector.selectedKeys()
selector.wakeup()

Selector. open () initializes a selector instance.

The select () method has multiple shapes:

Select () will be blocked until at least one channel is followed. Select (long timeout), timeout will also be returned. SelectNow () returns immediately.

The keys () and selectedKeys () methods must be distinguished here.:

The former returns the set of keys of the channel registered in the selector, and the latter is the set of keys of the selected channel, that is, the events of interest to the channel are ready.

Wakeup ()You can wake up the select () method that is being blocked to make it an immediate method.

(The more I feel, the more I write... Forget it, write it here ...)

 

 

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.