Java NIO类库Selector机制解析(Too many open files 和 No buffer space available)
一、 前言
自从 J2SE 1.4 版本以来, JDK 发布了全新的 I/O 类库,简称 NIO ,其不但引入了全新的高效的 I/O 机制,同时,也引入了多路复用的异步模式。 NIO 的包中主要包含了这样几种抽象数据类型:
Buffer :包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的 I/O 操作。
Charset :它提供 Unicode 字符串影射到字节序列以及逆映射的操作。
Channels :包含 socket , file 和 pipe 三种管道,都是全双工的通道。
Selector :多个异步 I/O 操作集中到一个或多个线程中(可以被看成是 Unix 中 select() 函数的面向对象版本)。
我的大学同学赵锟在使用 NIO 类库书写相关网络程序的时候,发现了一些 Java 异常 RuntimeException ,异常的报错信息让他开始了对 NIO 的 Selector 进行了一些调查。当赵锟对我共享了 Selector 的一些底层机制的猜想和调查时候,我们觉得这是一件很有意思的事情,于是在伙同赵锟进行过一系列的调查后,我俩发现了很多有趣的事情,于是导致了这篇文章的产生。这也是为什么本文的作者署名为我们两人的原因。
先要说明的一点是,赵锟和我本质上都是出身于 Unix/Linux/C/C++ 的开发人员,对于 Java ,这并不是我们的长处,这篇文章本质上出于对 Java 的 Selector 的好奇,因为从表面上来看 Selector 似乎做到了一些让我们这些 C/C++ 出身的人比较惊奇的事情。
下面让我来为你讲述一下这段故事。
二、 故事开始 : 让 C++程序员写 Java程序 !
没有严重内存问题,大量丰富的 SDK 类库,超容易的跨平台,除了在性能上有些微辞, C++ 出身的程序员从来都不会觉得 Java 是一件很困难的事情。当然,对于长期习惯于使用操作系统 API (系统调用 System Call )的 C/C++ 程序来说,面对 Java 中的比较“另类”地操作系统资源的方法可能会略感困惑,但万变不离其宗,只需要对面向对象的设计模式有一定的了解,用不了多长时间, Java 的 SDK 类库也能玩得随心所欲。
在使用 Java 进行相关网络程序的的设计时,出身 C/C++ 的人,首先想到的框架就是多路复用,想到多路复用, Unix/Linux 下马上就能让从想到 select, poll, epoll 系统调用。于是,在看到 Java 的 NIO 中的 Selector 类时必然会倍感亲切。稍加查阅一下 SDK 手册以及相关例程,不一会儿,一个多路复用的框架便呈现出来,随手做个单元测试,没啥问题,一切和 C/C++ 照旧。然后告诉兄弟们,框架搞定,以后咱们就在 Windows 上开发及单元测试,完成后到运行环境 Unix 上集成测试。心中并暗自念到,跨平台就好啊,开发活动都可以跨平台了。
然而,好景不长,随着代码越来越多,逻辑越来越复杂。好好的框架居然在 Windows 上单元测试运行开始出现异常,看着 Java 运行异常出错的函数栈,异常居然由 Selector.open() 抛出,错误信息居然是 Unable to establish loopback connection 。
“Selector.open() 居然报 loopback connection 错误,凭什么?不应该啊? open 的时候又没有什么 loopback 的 socket 连接,怎么会报这个错? ”
长期使用 C/C++ 的程序当然会对操作系统的调用非常熟悉,虽然 Java 的虚拟机搞的什么系统调用都不见了,但 C/C++ 的程序员必然要比 Java 程序敏感许多。
三、 开始调查 : 怎么 Java这么“傻” !
于是, C/C++ 的老鸟从 SystemInternals 上下载 Process Explorer 来查看一下究竟是什么个 Loopback Connection 。 果然,打开 java 运行进程,发现有一些自己连接自己的 localhost 的 TCP/IP 链接。于是另一个问题又出现了,
“ 凭什么啊?为什么会有自己和自己的连接?我程序里没有自己连接自己啊,怎么可能会有这样的链接啊?而自己连接自己的端口号居然是些奇怪的端口。 ”
问题变得越来越蹊跷了。难道这都是 Selector.open() 在做怪?难道 Selector.open() 要创建一个自己连接自己的链接?写个程序看看:
Java代码
import java.nio.channels.Selector; import java.lang.RuntimeException; import java.lang.Thread; public class TestSelector { private static final int MAXSIZE= 5 ; public static final void main( String argc[] ) { Selector [] sels = new Selector[ MAXSIZE]; try { for ( int i = 0 ;i< MAXSIZE ;++i ) { sels[i] = Selector.open(); //sels[i].close(); } Thread.sleep( 30000 ); } catch ( Exception ex ){ throw new RuntimeException( ex ); } } } 
Exception in thread "main" java.lang.RuntimeException: java.io.IOException: Unable to establish loopback connection at Test.main(Test.java:18) Caused by: java.io.IOException: Unable to establish loopback connection at sun.nio.ch.PipeImpl$Initializer.run(Unknown Source) at java.security.AccessController.doPrivileged(Native Method) at sun.nio.ch.PipeImpl. (Unknown Source) at sun.nio.ch.SelectorProviderImpl.openPipe(Unknown Source) at java.nio.channels.Pipe.open(Unknown Source) at sun.nio.ch.WindowsSelectorImpl.(Unknown Source) at sun.nio.ch.WindowsSelectorProvider.openSelector(Unknown Source) at java.nio.channels.Selector.open(Unknown Source) at Test.main(Test.java:15) Caused by: java.net.SocketException: No buffer space available (maximum connections reached?): connect at sun.nio.ch.Net.connect(Native Method) at sun.nio.ch.SocketChannelImpl.connect(Unknown Source) at java.nio.channels.SocketChannel.open(Unknown Source) ... 9 more
Exception in thread "main" java.lang.RuntimeException: java.io.IOException: Too many open files at Test1.main(Test1.java:19) Caused by: java.io.IOException: Too many open files at sun.nio.ch.IOUtil.initPipe(Native Method) at sun.nio.ch.EPollSelectorImpl.(EPollSelectorImpl.java:49) at sun.nio.ch.EPollSelectorProvider.openSelector(EPollSelectorProvider.java:18) at java.nio.channels.Selector.open(Selector.java:209) at Test1.main(Test1.java:15)


