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》