As we have said in last blog post, simple blocking IO model has its advantages, but also some limitations. In Java 1.4, it introduces the Non-blocking IO model.
Non-Blocking Model
Non-Blocking IO, which is often shorted as NIO, is a different way to abstract storage device:
NIO | IO |
---|---|
channel/buffer/selector | stream |
tend to support both read/write | simplex: input or output |
support multiple data source in one thread | one data source in one thread |
Then, we will dive into the three main abstractions of NIO: Channel, Buffer, Selector.
Channel
Channel is the abstraction of connection to entity (hardware device, file, socket etc), which can be used to read/write a block of datas. Channels are analogous to streams, but with a few differences:
- While streams are typically one-way (read or write), channels support read and write.
- Channels can be read and written in non-blocking way.
- Channels always read to, or write from, a buffer. All data that is sent to a channel must first be placed in a buffer. Any data that is read from a channel is read into a buffer.
Buffer
While stream I/O reads a character at a time, channel I/O reads a buffer at a time. Buffer has many sub types which holds different primitive types, like ByteBuffer
, CharBuffer
, IntBuffer
. It can be implemented in heap or direct memory, which has prefix like Heap
and Direct
separately, like DirectByteBufferR
& HeapByteBuffer
.
HeapBuffer
is easy to understand that this buffer is allocated on heap. What about DirectBuffer
? The buffer is directly allocated on native memory, which is not managed by heap. As we have introduced in first blog, the IO task is always done by OS kernel, that means data read from devices are first store in OS namespace, then copy to Java process. Using direct buffer can/may reduce the memory copy penalty (If the OS can read the data directly into this direct buffer, i.e. driver of device using the memory sharing as IPC method), but it also bring more allocation cost. The allocation cost of direct buffer is higher because the memory allocation in Java is a simple bump pointer in most cases, while direct buffer need request memory from OS kernel.
Because the direct buffer is not on heap, it means that it is not fully managed by Java garbage collector. In pre Java 9 environment, when the Java object of direct buffer is phantom-reachable, it will be enqueued in ReferenceQueue
and Cleaner
thread will run periodically to release the real memory. For more details, you may like to refer to here and here
Except the HeapBuffer
and DirectBuffer
, there exists a special one – MappedByteBuffer
, which represents file & memory at the same time. It can be get from the FileChannel
facilities. This channel can be used to map a region of a channel’s file directly into memory, which is called memory mapped file.
Selector
Selector is the core of multiplexed non-blocking IO. If a channel is a SelectableChannel
(network related channel, like SocketChannel
& DatagramChannel
), it can register itself to a Selector
. From now on, we can query the Selector
about readiness of those channels via select()
method. Selector#select
can be blocked, and we can continue when any of channel is ready. And it can also be non-blocking, giving us the flexibility of not waiting by using similar select(long timeout)
or selectNow()
.
Conclusion
Put them all in together, we can get a simple NIO program:
public static void main(String[] args) throws IOException {
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(9300));
server.configureBlocking(false);
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
for (Iterator<SelectionKey> it = selector.selectedKeys().iterator(); it.hasNext(); ) {
SelectionKey next = it.next();
it.remove();
try {
if (next.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) next.channel();
SocketChannel accept = channel.accept();
// have to config blocking mode or IllegalBlockingModeException
accept.configureBlocking(false);
SelectionKey key2 = accept.register(selector, SelectionKey.OP_WRITE);
ByteBuffer buffer = ByteBuffer.allocate(74);
// fill buffer ...
key2.attach(buffer);
} else if (next.isWritable()) {
SocketChannel client = ((SocketChannel) next.channel());
ByteBuffer buffer = (ByteBuffer) next.attachment();
// fill buffer ...
client.write(buffer);
}
} catch (IOException e) {
next.cancel();
next.channel().close();
}
}
}
}
It works, but somewhat tedious and easy to go wrong (have to remember to remove SelectionKey
when start to handle it). So, netty get its chance.
Ref
Written with StackEdit.
评论
发表评论