JAVA 包含最新的版本JDK1.8的NIO存在一些问题,这些问题须要在编写NIO程序时要格外关注: java
NIO是底层API,它的实现依赖于操做系统针对IO操做的APIs. 因此JAVA能在全部操做系统上实现统一的接口,并用一致的行为来操做IO是很伟大的。 网络
使用NIO会常常发现代码在Linux上正常运行,但在Windows上就会出现问题。因此编写程序,特别是NIO程序,须要在程序支持的全部操做系统上进行功能测试,不然你可能会碰到一些莫明的问题。
异步
NIO2看起来很理想,可是NIO2只支持Jdk1.7+,若你的程序在Java1.6上运行,则没法使用NIO2。另外,Java7的NIO2中没有提供DatagramSocket的支持,因此NIO2只支持TCP程序,不支持UDP程序 socket
操做系统底层知道如何处理这些被写入/读出,而且能以最有效的方式处理。若是要分割的数据在多个不一样的 ByteBuffer中,使用Gather/Scatter是比较好的方式。 工具
例如,你可能但愿header在一个ByteBuffer中,而body在另外的ByteBuffer中;
下图显示的是Scatter(分散),将ScatteringByteBuffer中的数据分散读取到多个ByteBuffer中:
性能
下图显示的是Gather(聚合),将多个ByteBuffer的数据写入到GatheringByteChannel:
测试
惋惜Gather/Scatter功能会致使内存泄露,知道Java7才解决内存泄露问题。使用这个功能必须当心编码和Java版本
编码
Linux-like OSs的选择器使用的是epoll-IO事件通知工具。操做系统使用这一高性能的技术与网络协议栈异步工做。
不幸的是,即便是如今,著名的epoll-bug也可能会致使无效的状态选择和100%的CPU利用率。要解决epoll-bug的惟一方法是回收旧的选择器,将先前注册的通道实例转移到新建立的选择器上。 spa
NIO中对epoll问题的解决方案是有限制的,Netty提供了更好的解决方案。下面是epoll-bug的一个例子 操作系统
import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class PlainNioEchoServer { public static void main(String[] args) throws IOException { serve(8118); } public static void serve(int port) throws IOException { System.out.println("Listening for connections on port " + port); ServerSocketChannel serverChannel = ServerSocketChannel.open(); ServerSocket ss = serverChannel.socket(); InetSocketAddress address = new InetSocketAddress(port); ss.bind(address); serverChannel.configureBlocking(false); Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { try { // 这里发生的是,无论有没有已选择的SelectionKey,Selector.select()方法老是不会阻塞而且会马上返回。 // 这违反了Javadoc中对Selector.select()方法的描述, // Javadoc中的描述:Selector.select() must not unblock if nothing is selected. // (Selector.select()方法若未选中任何事件将会阻塞。) System.out.println("............."); selector.select(); } catch (IOException ex) { ex.printStackTrace(); // handle in a proper way break; } Set readyKeys = selector.selectedKeys(); Iterator iterator = readyKeys.iterator(); // 该值将永远是假的,代码将持续消耗你的CPU资源。 //这会有一些反作用,由于CPU消耗完了就没法再去作其余任何的工做。 while (iterator.hasNext()) { SelectionKey key = (SelectionKey) iterator.next(); iterator.remove(); try { if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel client = server.accept(); System.out.println("Accepted connection from " + client); client.configureBlocking(false); client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, ByteBuffer.allocate(100)); } if (key.isReadable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); client.read(output); } if (key.isWritable()) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer output = (ByteBuffer) key.attachment(); output.flip(); client.write(output); output.compact(); } } catch (IOException ex) { key.cancel(); try { key.channel().close(); } catch (IOException cex) { } } } } } }
运行程序后,客户端链接进来,什么工做都不作,但CPU利用率却已经达到100%
这些仅仅是在使用NIO时可能会出现的一些问题。不幸的是,虽然在这个领域发展了多年,问题依然存在;
幸运的是,Netty给了你比较好的解决方案。
《Netty in Action》