-
Notifications
You must be signed in to change notification settings - Fork 0
01 Core Overview
Keywords: Java NIO, selector tutorial, non-blocking IO Java, Java event loop, Java concurrency
Java NIO selectors are the primary mechanism for building scalable network applications. However, to truly master them, a developer must look beyond the java.nio package and understand how the JVM interacts with the underlying Operating System kernel.
Instead of a thread-per-connection model, the Selector enables a single-threaded event-driven architecture by acting as an event multiplexer.
When you call Selector.open(), Java doesn't just create an object; it requests a specific resource from the OS.
The Selector's behavior changes based on your operating system. Java uses a provider-based architecture to call the most efficient native APIs available:
-
Linux: Uses
epoll. This is highly scalable as it doesn't poll every file descriptor. -
macOS/BSD: Uses
kqueue. -
Windows: Uses
selectorIOCP(Input/Output Completion Ports).
The core of the architecture is the Event Loop. Here is what happens internally during a typical select() call:
When you call channel.register(selector, OP_READ), a SelectionKey is created. This key is a token representing the registration in the Selector's internal data structures.
Note: The channel MUST be in non-blocking mode (
channel.configureBlocking(false)) before registration.
When selector.select() is invoked:
- The thread enters a blocked state (efficiently, without consuming CPU).
- The JVM makes a native system call (e.g.,
epoll_wait). - The OS monitors the file descriptors associated with the registered channels.
- As soon as one or more channels are ready (e.g., data arrived in the TCP buffer), the OS wakes up the thread.
The Selector populates the Selected Key Set. Your code then iterates through these keys to handle specific events:
-
isAcceptable(): New connection is waiting. -
isReadable(): Data is ready to be pulled into aByteBuffer. -
isWritable(): The socket's send buffer has space for more data.
One of the most common points of confusion is the difference between what you want and what is ready:
- interestOps: The set of events you told the Selector to watch for (e.g., "Tell me when this channel is readable").
- readyOps: The set of events the OS reported as actually occurring (e.g., "This channel is now readable").
Using selector.selectNow() in a tight loop without any backoff or logic causes the thread to poll constantly, leading to 100% CPU usage. Always prefer the blocking select() or select(timeout) unless you have a specific non-blocking requirement.
The Selector adds keys to the selectedKeys() set but never removes them.
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// Handle the event...
iter.remove(); // CRITICAL: You must manually remove the key!
}