Java NIO类库Selector机制解析(上)
赵锟 陈皓
http://blog.csdn.net/haoel
一、 前言
自从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()要创建一个自己连接自己的链接?写个程序看看:
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 );
}
}
}
这个程序什么也没有,就是做5次Selector.open(),然后休息30秒,以便我使用Process Explorer工具来查看进程。程序编译没有问题,运行起来,在Process Explorer中看到下面的对话框:(居然有10个连接,从连接端口我们可以知道,互相连接, 如:第一个连第二个,第二个又连第一个)
<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1026" style="WIDTH: 327.75pt; HEIGHT: 367.5pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:%5CDOCUME~1%5CHAO~1.CHE%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image001.png"></imagedata></shape>
不由得赞叹我们的Java啊,先不说这是不是一件愚蠢的事。至少可以肯定的是,Java在消耗宝贵的系统资源方面,已经可以赶的上某些蠕虫病毒了。
如果不信,不妨把上面程序中的那个MAXSIZE的值改成65535试试,不一会你就会发现你的程序有这样的错误了:(在我的XP机器上大约运行到2000个Selector.open() 左右)
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.<init>(Unknown Source)
at sun.nio.ch.SelectorProviderImpl.openPipe(Unknown Source)
at java.nio.channels.Pipe.open(Unknown Source)
at sun.nio.ch.WindowsSelectorImpl.<init>(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
四、 继续调查 : 如此跨平台
当然,没人像我们这么变态写出那么多的Selector.open(),但这正好可以让我们来明白Java背着大家在干什么事。上面的那些“愚蠢连接”是在Windows平台上,如果不出意外,Unix/Linux下应该也差不多吧。
于是我们把上面的程序放在Linux下跑了跑。使用netstat 命令,并没有看到自己和自己的Socket连接。貌似在Linux上使用了和Windows不一样的机制?!
如果在Linux上不建自己和自己的TCP连接的话,那么文件描述符和端口都会被省下来了,是不是也就是说我们调用65535个Selector.open()的话,应该不会出现异常了。
可惜,在实现运行过程序当中,还是一样报错:(大约在400个Selector.open()左右,还不如Windows)
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.<init>(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)
我们发现,这个异常错误是“Too many open files”,于是我想到了使用lsof命令来查看一下打开的文件。
看到了有一些pipe文件,一共5对,10个(当然,管道从来都是成对的)。如下图所示。
<shape id="_x0000_i1025" style="WIDTH: 6in; HEIGHT: 296.25pt" type="#_x0000_t75"><imagedata o:title="" src="file:///C:%5CDOCUME~1%5CHAO~1.CHE%5CLOCALS~1%5CTemp%5Cmsohtml1%5C01%5Cclip_image003.png"><font face="Times New Roman" size="3"></font></imagedata></shape>
可见,Selector.open()在Linux下不用TCP连接,而是用pipe管道。看来,这个pipe管道也是自己给自己的。所以,我们可以得出下面的结论:
1)Windows下,Selector.open()会自己和自己建立两条TCP链接。不但消耗了两个TCP连接和端口,同时也消耗了文件描述符。
2)Linux下,Selector.open()会自己和自己建两条管道。同样消耗了两个系统的文件描述符。
估计,在Windows下,Sun的JVM之所以选择TCP连接,而不是Pipe,要么是因为性能的问题,要么是因为资源的问题。可能,Windows下的管道的性能要慢于TCP链接,也有可能是Windows下的管道所消耗的资源会比TCP链接多。这些实现的细节还有待于更为深层次的挖掘。
但我们至少可以了解,原来Java的Selector在不同平台上的机制。
点此查看本文下篇>>>>
(转载时请注明作者和出处。未经许可,请勿用于商业用途)
更多文章请访问我的Blog: http://blog.csdn.net/haoel
分享到:
相关推荐
Java_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.docJava_NIO类库Selector机制解析.doc
"Java NIO Selector 机制解析" ...了解Java NIO类库和Selector机制可以帮助程序员更好地使用Java NIO类库,提高I/O操作的性能。但需要注意到Java虚拟机对操作系统调用的影响,避免出现异常和错误。
以上只是Java2类库中的部分重要知识点,实际上,Java类库非常庞大,涵盖了图形用户界面、数据库连接、国际化、数学计算等多个领域,是开发者构建各种类型应用的基石。深入学习和理解这些类库,能显著提升开发效率和...
【标签】"Java"明确了这个项目是用Java语言编写的,Java作为一门广泛应用的编程语言,其丰富的类库和强大的跨平台能力使得开发网络服务器成为可能。 至于【压缩包子文件的文件名称列表】中的“tiny-web-server-...
Java以其跨平台的特性、丰富的类库以及强大的性能,成为了后端开发的首选语言之一,尤其适合大型、复杂的游戏服务器开发。 1. **网络编程**:游戏服务器的核心是处理客户端的连接请求和数据传输。Java的Socket编程...
Servlet API和NIO(New IO)是Java编程中两个重要的概念,它们在处理网络I/O操作上有着不同的优势。Servlet API是Java服务器端编程的重要组成部分,主要用于构建动态Web应用程序,而NIO则是一种非阻塞的I/O模型,...
1. **NIO(Non-blocking I/O)**:Java的非阻塞I/O模型,提供了通道(Channel)和选择器(Selector)等概念,极大地提高了处理大量并发连接的能力。在`sun.nio`包下,你可以找到`sun.nio.ch`子包,它包含了Java NIO...
- Java NIO还包括缓冲区(Buffer)、通道(Channel)、选择器(Selector)等概念。 5. JVM类加载机制 - 类加载过程包括加载、验证、准备、解析、初始化等步骤。 - 类加载器分为启动类加载器(BootstrapClassLoader)、...
在`java.nio`包中,`FileChannel`、`SocketChannel`和`Selector`等类是核心部分,通过查看其源码,开发者可以学习到如何高效地进行多路复用I/O,以及如何利用非阻塞特性来优化网络通信。 最后,`misc`(杂项)一词...
Java是一种广泛使用的面向对象的编程语言,以其跨平台、高性能和丰富的类库而备受赞誉。在IT行业,尤其是软件开发领域,Java工程师是需求量极大的职位。为了在Java面试中脱颖而出,深入理解Java语言的核心概念和技术...
JDK(Java Development Kit)是Oracle公司提供的用于开发Java应用程序的重要软件包,它包含了Java编译器、Java虚拟机(JVM)、Java类库以及各种开发和调试工具,是Java开发的基础。 JDK 1.4版本是Java历史上的一个...
4. **I/O与NIO**:Java API 1.6不仅有传统的基于流的I/O,还引入了非阻塞I/O(New IO,即NIO)框架,提供了一种更高效的数据传输方式,包括`java.nio`包中的通道(Channel)、缓冲区(Buffer)和选择器(Selector)...
文件《JAVA核心知识点整理.pdf》作为Java程序员面试准备资料,提供了一个全面的Java知识点回顾,涵盖JVM运行机制、多线程编程、集合框架、IO/NIO、类加载机制等内容。这些知识点对于应聘者理解Java技术栈、提升编程...
2. **IO流**:Java 1.6对输入/输出流进行了优化,提供了NIO(New Input/Output)框架,它支持选择器(Selector)和通道(Channel)等特性,提高了IO操作的性能和并发性。 3. **网络编程**:Java API 1.6提供Socket...
相对于传统的IO模型,NIO提供了选择器(Selector)和通道(Channel)等机制,可以实现多路复用,提高网络通信的效率,尤其适合高并发场景。 通过这个Java网络教程,学习者将能够熟练地运用Java进行网络编程,包括...
3. **NIO(非阻塞I/O)**:Java NIO(New Input/Output)是Java 1.4引入的新特性,它提供了与传统IO不同的I/O操作方式,支持选择器(Selector)、通道(Channel)和缓冲区(Buffer)。NIO允许程序在数据准备好时进行...
Java IO还包括对象序列化(Serialization)机制,允许将Java对象转换为字节流,便于存储或在网络上传输。ObjectInputStream和ObjectOutputStream分别用于读写序列化对象。 在实际开发中,Java IO广泛应用于文件操作...
5. **java.nio**:非阻塞I/O,提供了Channel、Buffer和Selector等,提高了I/O性能。 6. **java.awt**和**javax.swing**:这两个包用于创建图形用户界面,其中java.awt提供基本组件,javax.swing提供了更为高级和...
`java.nio`包提供了选择器(Selector)、通道(Channel)和缓冲区(Buffer)等组件,可以实现高并发和低延迟的网络服务。AIO(Java NIO.2)引入了`AsynchronousServerSocketChannel`和`AsynchronousSocketChannel`,...
Java 1.6 API 中文版是针对Java SE(标准版)6.0平台的一份完整的官方文档,它为开发者提供了详细的类库、接口和方法的说明,是学习和开发Java应用程序的重要参考资料。这份API文档涵盖了Java语言的核心库,包括基础...