`

Java NIO——Selector机制解析一《转》

阅读更多

一、  前言

 

自从J2SE 1.4版本以来,JDK发布了全新的I/O类库,简称NIO,其不但引入了全新的高效的I/O机制,同时,也引入了多路复用的异步模式。NIO的包中主要包含了这样几种抽象数据类型:

 

  • Buffer:包含数据且用于读写的线形表结构。其中还提供了一个特殊类用于内存映射文件的I/O操作。
  • Charset:它提供Unicode字符串影射到字节序列以及逆映射的操作。
  • Channels:包含socketfilepipe三种管道,都是全双工的通道。
  • Selector:多个异步I/O操作集中到一个或多个线程中(可以被看成是Unixselect()函数的面向对象版本)。

 

我的大学同学赵锟在使用NIO类库书写相关网络程序的时候,发现了一些Java异常RuntimeException,异常的报错信息让他开始了对NIOSelector进行了一些调查。当赵锟对我共享了Selector的一些底层机制的猜想和调查时候,我们觉得这是一件很有意思的事情,于是在伙同赵锟进行过一系列的调查后,我俩发现了很多有趣的事情,于是导致了这篇文章的产生。这也是为什么本文的作者署名为我们两人的原因。

 

先要说明的一点是,赵锟和我本质上都是出身于Unix/Linux/C/C++的开发人员,对于Java,这并不是我们的长处,这篇文章本质上出于对JavaSelector的好奇,因为从表面上来看Selector似乎做到了一些让我们这些C/C++出身的人比较惊奇的事情。

 

下面让我来为你讲述一下这段故事。

 

二、  故事开始: C++程序员写Java程序!

 

没有严重内存问题,大量丰富的SDK类库,超容易的跨平台,除了在性能上有些微辞,C++出身的程序员从来都不会觉得Java是一件很困难的事情。当然,对于长期习惯于使用操作系统API(系统调用System Call)的C/C++程序来说,面对Java中的比较“另类”地操作系统资源的方法可能会略感困惑,但万变不离其宗,只需要对面向对象的设计模式有一定的了解,用不了多长时间,JavaSDK类库也能玩得随心所欲。

 

在使用Java进行相关网络程序的的设计时,出身C/C++的人,首先想到的框架就是多路复用,想到多路复用,Unix/Linux下马上就能让从想到select, poll, epoll系统调用。于是,在看到JavaNIO中的Selector类时必然会倍感亲切。稍加查阅一下SDK手册以及相关例程,不一会儿,一个多路复用的框架便呈现出来,随手做个单元测试,没啥问题,一切和C/C++照旧。然后告诉兄弟们,框架搞定,以后咱们就在Windows上开发及单元测试,完成后到运行环境Unix上集成测试。心中并暗自念到,跨平台就好啊,开发活动都可以跨平台了。

 

然而,好景不长,随着代码越来越多,逻辑越来越复杂。好好的框架居然在Windows上单元测试运行开始出现异常,看着Java运行异常出错的函数栈,异常居然由Selector.open()抛出,错误信息居然是Unable to establish loopback connection

 

“Selector.open()居然报loopback connection错误,凭什么?不应该啊?open的时候又没有什么loopbacksocket连接,怎么会报这个错?

 

长期使用C/C++的程序当然会对操作系统的调用非常熟悉,虽然Java的虚拟机搞的什么系统调用都不见了,但C/C++的程序员必然要比Java程序敏感许多。

 

三、  开始调查: 怎么Java这么“傻”!

 

于是,C/C++的老鸟从SystemInternals上下载Process Explorer来查看一下究竟是什么个Loopback Connection果然,打开java运行进程,发现有一些自己连接自己的localhostTCP/IP链接。于是另一个问题又出现了,

 

凭什么啊?为什么会有自己和自己的连接?我程序里没有自己连接自己啊,怎么可能会有这样的链接啊?而自己连接自己的端口号居然是些奇怪的端口。

 

问题变得越来越蹊跷了。难道这都是Selector.open()在做怪?难道Selector.open()要创建一个自己连接自己的链接?写个程序看看:

 

import java.nio.channels.Selector;

import java.lang.RuntimeException;

import java.lang.Thread;

publicclass TestSelector {

    privatestaticfinalint MAXSIZE=5;

    publicstaticfinalvoid 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 ){

                thrownew RuntimeException( ex );

            }

    }

}

 

这个程序什么也没有,就是做5Selector.open(),然后休息30秒,以便我使用Process Explorer工具来查看进程。程序编译没有问题,运行起来,在Process Explorer中看到下面的对话框:(居然有10个连接,从连接端口我们可以知道,互相连接,如:第一个连第二个,第二个又连第一个

 

 

 

 

 

不由得赞叹我们的Java啊,先不说这是不是一件愚蠢的事。至少可以肯定的是,Java在消耗宝贵的系统资源方面,已经可以赶的上某些蠕虫病毒了。

 

如果不信,不妨把上面程序中的那个MAXSIZE的值改成65535试试,不一会你就会发现你的程序有这样的错误了:(在我的XP机器上大约运行到2000Selector.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连接的话,那么文件描述符和端口都会被省下来了,是不是也就是说我们调用65535Selector.open()的话,应该不会出现异常了。

 

可惜,在实现运行过程序当中,还是一样报错:(大约在400Selector.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个(当然,管道从来都是成对的)。如下图所示。

 

 

 

 

可见,Selector.open()Linux下不用TCP连接,而是用pipe管道。看来,这个pipe管道也是自己给自己的。所以,我们可以得出下面的结论:

 

1)Windows下,Selector.open()会自己和自己建立两条TCP链接。不但消耗了两个TCP连接和端口,同时也消耗了文件描述符。

2)Linux下,Selector.open()会自己和自己建两条管道。同样消耗了两个系统的文件描述符。

 

估计,在Windows下,SunJVM之所以选择TCP连接,而不是Pipe,要么是因为性能的问题,要么是因为资源的问题。可能,Windows下的管道的性能要慢于TCP链接,也有可能是Windows下的管道所消耗的资源会比TCP链接多。这些实现的细节还有待于更为深层次的挖掘。

 

但我们至少可以了解,原来JavaSelector在不同平台上的机制。

五、  迷惑不解: 为什么要自己消耗资源?

 

令人不解的是为什么我们的JavaNew I/O要设计成这个样子?如果说老的I/O不能多路复用,如下图所示,要开N多的线程去挨个侦听每一个Channel (文件描述符) ,如果这样做很费资源,且效率不高的话。那为什么在新的I/O机制依然需要自己连接自己,而且,还是重复连接,消耗双倍的资源?

 

通过WEB搜索引擎没有找到为什么。只看到N多的人在报BUG,但SUN却没有任何解释。

 

下面一个图展示了,老的IO和新IO的在网络编程方面的差别。看起来NIO的确很好很强大。但似乎比起C/C++来说,Java的这种实现会有一些不必要的开销。

 

 

 

 

六、  它山之石: ApacheMina框架了解Selector

 

上面的调查没过多长时间,正好同学赵锟的一个同事也在开发网络程序,这位仁兄使用了ApacheMina框架。当我们把Mina框架的源码研读了一下后。发现在Mina中有这么一个机制:

 

1)Mina框架会创建一个Work对象的线程。

2)Work对象的线程的run()方法会从一个队列中拿出一堆Channel,然后使用Selector.select()方法来侦听是否有数据可以读/写。

3)最关键的是,在select的时候,如果队列有新的Channel加入,那么,Selector.select()会被唤醒,然后重新select最新的Channel集合。

4)要唤醒select方法,只需要调用Selectorwakeup()方法。

 

对于熟悉于系统调用的C/C++程序员来说,一个阻塞在select上的线程有以下三种方式可以被唤醒:

1)  有数据可读/写,或出现异常。

2)  阻塞时间到,即time out

3)  收到一个non-block的信号。可由killpthread_kill发出。

所以,Selector.wakeup()要唤醒阻塞的select,那么也只能通过这三种方法,其中:

 

1)第二种方法可以排除,因为select一旦阻塞,应无法修改其time out时间。

2)而第三种看来只能在Linux上实现,Windows上没有这种信号通知的机制。

 

所以,看来只有第一种方法了。再回想到为什么每个Selector.open(),在Windows会建立一对自己和自己的loopbackTCP连接;在Linux上会开一对pipepipeLinux下一般都是成对打开),估计我们能够猜得出来——那就是如果想要唤醒select,只需要朝着自己的这个loopback连接发点数据过去,于是,就可以唤醒阻塞在select上的线程了。

 

七、  真相大白: 可爱的Java你太不容易了

 

使用Linux下的strace命令,我们可以方便地证明这一点。参看下图。图中,请注意下面几点:

1)  26654是主线程,之前我输出notify the select字符串是为了做一个标记,而不至于迷失在大量的strace log中。

2)  26662是侦听线程,也就是select阻塞的线程。

3)  图中选中的两行。26654write正是wakeup()方法的系统调用,而紧接着的就是26662epoll_wait的返回。

 

 

 

从上图可见,这和我们之前的猜想正好一样。可见,JDKSelector自己和自己建的那些TCP连接或是pipe,正是用来实现Selectornotifywakeup的功能的。

 

这两个方法完全是来模仿Linux中的的killpthread_kill给阻塞在select上的线程发信号的。但因为发信号这个东西并不是一个跨平台的标准(pthread_kill这个系统调用也不是所有Unix/Linux都支持的),而pipe是所有的Unix/Linux所支持的,但Windows又不支持,所以,Windows用了TCP连接来实现这个事。

 

关于Windows,我一直在想,Windows的防火墙的设置是不是会让Java的类似的程序执行异常呢?呵呵。如果不知道JavaSDK有这样的机制,谁知道会有多少个程序为此引起的问题度过多少个不眠之夜,尤其是Java程序员。

 

原文地址:http://blog.csdn.net/haoel/article/details/2224055

分享到:
评论

相关推荐

    Java NIO——Selector机制解析三(源码分析)

    本文将深入探讨Java NIO中的Selector机制,并通过源码分析来理解其实现原理。 Selector机制是Java NIO中的核心组件,它允许单线程同时监控多个通道(Channels)的状态变化,例如连接就绪、数据可读或可写等。这种...

    java常用工具类——个人总结

    - `java.nio` 包中的非阻塞I/O模型,如`Selector`、`Channel`、`Buffer`,提供了更高的并发性能。 8. **线程工具类**: - `java.util.concurrent` 包提供了丰富的并发工具,如`ExecutorService`、`Future`、`...

    java网络编程(中文)

    总的来说,Java网络编程是一个涉及广泛且深奥的领域,从基础的Socket通信到高级的NIO和安全机制,都需要深入理解和实践。通过学习,开发者能够构建出功能强大的网络应用,例如Web服务、分布式系统或实时通信工具。

    基于时间的NIO多线程服务器

    ### 基于时间的NIO多线程服务器——深入解析与关键技术点 #### 引言 在服务器端编程领域,随着互联网应用的不断发展,如何高效处理大量的并发连接成为了一个重要议题。Java NIO(非阻塞I/O)作为一种先进的I/O处理...

    利用JDK7的NIO2.0进行I/O读写和监视

    在Java编程领域,JDK 7引入了一个重要的更新——NIO2.0,也被称为“New I/O 2.0”或“AIO”(Asynchronous I/O)。这个更新极大地提升了Java处理I/O操作的能力,特别是在文件系统交互和网络通信方面。NIO2.0主要增加...

    学习笔记——资料

    【Java学习笔记——全面解析】 Java作为一种广泛应用的高级编程语言,是软件开发领域的核心力量。这份"学习笔记——资料"涵盖了Java学习的各个方面,旨在帮助初学者和有经验的开发者巩固基础,提升技能。以下是对这...

    tomcat 分配请求之——socket获取请求

    NIO模型在Java中通过Selector和Channel实现,能更有效地处理大量并发连接,而BIO模型则更为简单,但对高并发场景支持较差。根据Tomcat的配置,它会选择合适的I/O模型来读取Socket上的字节流,并将其转化为HTTP请求...

    t通天塔

    《通天塔——深入解析Java NIO》 Java NIO(New Input/Output)是Java在JDK 1.4引入的一个新特性,它为Java提供了另一种与I/O相关的编程模型,相较于传统的IO,NIO具有更高的性能和更好的灵活性。在传统IO中,我们...

    JAVA核心知识点整理.pdf

    在NIO部分,文档详细讲解了缓冲区、通道(Channel)、非阻塞IO的实现、选择器(Selector)等概念和机制。 4. JVM类加载机制: JVM类加载机制负责将Java类装入JVM中执行。这部分内容涉及到类加载过程中的各个步骤...

    Netty In Action中文版

    - **解决 Java NIO 的限制**:针对 Java NIO 中存在的问题(如内存泄漏、Selector 的使用复杂度高等),Netty 提供了一系列优化方案,确保了框架的稳定性和可靠性。 #### 4. 总结 Netty 作为一款高性能的网络通信...

    java技能百练--网络篇

    Java技能百练——网络篇是针对Java开发者在网络编程方面的深入学习和实践。在这个主题中,我们将探讨Java在处理网络通信、数据传输以及网络服务开发等多个关键领域的应用。下面,我们将详细解析Java网络编程的一些...

    Java+TCPIP+Socket编程(中文版)

    ### Java+TCPIP+Socket编程(中文版) —— 关键知识点详解 #### 第1章 简介 - **计算机网络、分组报文和协议** 计算机网络是通过通信设备和线路将地理位置分散、功能独立的多个计算机系统互连起来,以功能完善的...

    Netty源码解析-服务启动过程.pdf

    ### Netty源码解析——服务启动过程 #### 一、Netty概述 Netty是一个高性能、异步事件驱动的网络应用框架,它被广泛应用于快速开发高性能协议服务器和客户端。Netty通过高度优化的设计和实现提供了低延迟和高吞吐...

    JAVA就业2023相关面试题

    14. **NIO(New IO)与NIO.2**:理解通道(Channel)、缓冲区(Buffer)、选择器(Selector)的概念,以及非阻塞I/O的优势。 15. **Java 8新特性**:掌握Lambda表达式、Stream API、Optional类、日期时间API、函数...

    Core Java技术面试资料.zip

    《深入解析Core Java技术面试》 Core Java是Java编程的基础,涵盖了从基本语法到高级特性的广泛知识领域。对于任何想要在Java开发领域深化技能或准备面试的人来说,掌握Core Java技术至关重要。本资料集合旨在帮助...

    Java_TCPIP_Socket编程(中文版)

    为了确保这些分组能够正确地从发送方传送到接收方,并且按照正确的顺序重组,就需要有一套约定好的规则——即协议。 - **关于地址**:网络通信中,每台设备都有一个唯一的地址,称为IP地址,用于标识其在网络中的...

    java4:Java 4 课程作业。

    `java.nio`包包含了与NIO相关的类,如`FileChannel`、`SocketChannel`和`Selector`等。 在多线程处理上,Java 4引入了`java.util.concurrent`包,这是Java并发库的基础,包含了各种高级并发工具,如`...

    Netty源码解析1

    Netty 解决了一个关键问题——拆包和粘包。在TCP传输中,数据可能会被拆分成多个数据包发送,或者多个小数据包合并成一个大数据包发送,这给处理数据带来了挑战。Netty 自带的编解码器就是为了处理这个问题,确保...

    JAVA面试题

    - NIO(New IO):理解非阻塞IO模型,Channel、Buffer和Selector的作用。 7. **网络编程** - Socket编程:建立TCP/IP连接,发送和接收数据。 - HttpURLConnection:HTTP协议的Java实现,用于发送HTTP请求。 8. ...

Global site tag (gtag.js) - Google Analytics