转自:http://blog.csdn.net/haoel/article/details/2224055

一、  前言

自从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 {

;

    public static final void main(
String argc[] ) {

        Selector [] sels = new Selector[ MAXSIZE];

 

            try{

 ;i<
MAXSIZE ;++i ) {

                    sels[i] = Selector.open();

                    //sels[i].close();

                }

);

            }catch( Exception ex ){

                throw new RuntimeException( ex );

            }

    }

}

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

不由得赞叹我们的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个(当然,管道从来都是成对的)。如下图所示。

可见,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在不同平台上的机制。

NIO组件Selector工作机制详解(上)的更多相关文章

  1. NIO组件Selector工作机制详解(下)

    转自:http://blog.csdn.net/haoel/article/details/2224069 五.  迷惑不解 : 为什么要自己消耗资源? 令人不解的是为什么我们的Java的New I/ ...

  2. Hadoop框架:NameNode工作机制详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.存储机制 1.基础描述 NameNode运行时元数据需要存放在内存中,同时在磁盘中备份元数据的fsImage,当元数据有更新或者添加元数据 ...

  3. Hadoop框架:DataNode工作机制详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.工作机制 1.基础描述 DataNode上数据块以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是数据块元数据包括长度.校验.时 ...

  4. Session的工作机制详解和安全性问题(PHP实例讲解)

    我们先简单的了解一些http的知识,从而理解该协议的无状态特性.然后,学习一些关于cookie的基本操作.最后,我会一步步阐述如何使用一些简单,高效的方法来提高你的php应用程序的安全性以及稳定行. ...

  5. JVM结构、GC工作机制详解

      JVM结构.内存分配.垃圾回收算法.垃圾收集器.下面我们一一来看. 一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知,JVM主要包括四个部分 ...

  6. JVM结构、GC工作机制详解(转)

    原文地址:http://blog.csdn.NET/tonytfjing/article/details/44278233 JVM结构.内存分配.垃圾回收算法.垃圾收集器.下面我们一一来看. 一.JV ...

  7. 【转载】JVM结构、GC工作机制详解

    文章主要分为以下四个部分 JVM结构.内存分配.垃圾回收算法.垃圾收集器.下面我们一一来看. 一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知, ...

  8. 【系统之音】WindowManager工作机制详解

    前言 目光所及,皆有Window!Window,顾名思义,窗口,它是应用与用户交互的一个窗口,我们所见到视图,都对应着一个Window.比如屏幕上方的状态栏.下方的导航栏.按音量键调出来音量控制栏.充 ...

  9. Android Touch事件传递机制详解 上

    最近总是遇到关于Android Touch事件的问题,如:滑动冲突的问题,以前也花时间学习过Android Touch事件的传递机制,可以每次用起来的时候总是忘记了,索性自己总结一下写篇文章避免以后忘 ...

随机推荐

  1. C#程序:如何创建xml文件以及xml文件的增、删、改、查

    其实今天的这篇博文 ,是对请几天发表的博文的一个总结,只是想把xml文件的增删改查结合起来,这样更容易让初学的朋友理解,废话也不多说了,开始吧! 下面是我把我在vs环境下写的代码ctrl+V然后ctr ...

  2. 中国天气网接口返回json格式分析及接口(XML、图片接口)说明

    实时天气: city        "北京"//城市 cityid      "101010100"//城市编码 temp        "17&qu ...

  3. C++11老关键字的新含义(auto, using,extern)

    http://blog.csdn.net/cnsword/article/details/8034947 公司可以使用c++11.看大牛的代码模仿使用,所以现在已经不知道什么使用的是c++的语法还是c ...

  4. 【C语言】printf函数详解

    C语言printf函数详解 一.相关基础知识 请求printf()打印变量的指令取决于变量的类型,例如打印整数用%d符号,打印字符用%c符号,这些符号称为转换说明(conversion specifi ...

  5. git 基础命令

    1.git init git 初始化仓库 2.git add . git 添加全部文件 3.git add xxx.txt   git 添加单独文件 4.git commit -m "提交的 ...

  6. Warning: Invalid argument supplied for foreach()

    经常对提交过来的数据进行双重循环,但是为空时会报错:Warning: Invalid argument supplied for foreach() 如下解决即可:foreach($data[$i]  ...

  7. ligerUI路径问题

    ligerUI放mv的Content目录下,路径为固定的并且必须引进一下文件 <link href="~/Content/Ligerui/Source/lib/ligerUI/skin ...

  8. java程序编译

    Empoyee.java package Company; public class Empoyee { String name = ""; public Empoyee(Stri ...

  9. Contest20140710 loop bellman-ford求负环&&0/1分数规划

    loop|loop.in|loop.out 题目描述: 给出一个有向带权图,权为边权,求一个简单回路,使其平均边权最小. 简单回路指不多次经过同一个点的回路. 输入格式: 第一行两个整数,表示图的点数 ...

  10. document.styleSheets

    伪元素是不能选中的,如果非要改他的样式,两个方法. 静态方法: addClass的时候,新add的class带有新的伪元素. 动态方法: 如果知道它在document.styleSheets对象中的位 ...