陈皓专栏 【空谷幽兰,心如皓月】

芝兰生于深谷,不以无人而不芳;君子修道立德,不为困穷而改节。

陈皓ID:haoel
455831次访问,排名97好友15人,关注者28
暂无
haoel的文章
原创 71 篇
翻译 0 篇
转载 0 篇
评论 1022 篇
陈皓的公告
Email & MSN
haoel@hotmail.com
最近评论
hmilmchm:崇拜ing,我最近两年一直在迷茫中~~~
wsliulao5:同楼上,也不理解 2.4、臭虫Bug
ropert:写得很啊,幸苦了.
hellocpp:C++本身是门很简单的语言,干吗弄那么复杂呢。我就从来不用虚函数,C++不是写的很好么。
poet:不喜欢C++总可以找出理由。虽然个人看法C++并不是一个很好的语言。但是:

1。C++本身是个不错的玩具,它具有所有编程语言中最复杂的语法和最复杂的编译器,对于语法狂人来说,具有其独特的魅力。

2。C++上面的特性,导致了,写出一份让90%的C++程序员都看不懂的程序非常容易。例如说多使用一些模板类,函数对象,函数适配器,bind2nd之类的。估计……
文章分类
收藏
    相册
    我的BLOG
    耗子小筑(非技术)(RSS)
    陈皓专栏(技 术)(RSS)
    存档
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 Java NIO 类库Selector机制解析(续)收藏

    Java NIO 类库Selector机制解析(续)

     

    陈皓

    http://blog.csdn.net/haoel

     

     

    在前些天的《Java NIO类库Selector机制解析》文章中,我们知道了下面的事情:

     

    1)SunJVM在实现Selector上,在LinuxWindows平台下的细节。

    2)Selector类的wakeup()方法如何唤醒阻塞在select()系统调用上的细节。

     

    先给大家做一个简单的回顾,在Windows下,SunJava虚拟机在Selector.open()时会自己和自己建立loopbackTCP链接;在Linux下,Selector会创建pipe。这主要是为了Selector.wakeup()可以方便唤醒阻塞在select()系统调用上的线程(通过向自己所建立的TCP链接和管道上随便写点什么就可以唤醒阻塞线程)

     

    我们知道,无论是建立TCP链接还是建立管道都会消耗系统资源,而在Windows上,某些Windows上的防火墙设置还可能会导致JavaSelector因为建立不起loopbackTCP链接而出现异常。

     

    而在我的另一篇文章《GDB调试Java程序》中介绍了另一个Java的解释器——GNUgij,以及编译器gcj,不但可以比较高效地运行Java程序,而且还可以把Java程序直接编译成可执行文件。

     

    GNU的之所以要重做一个Java的编译和解释器,其一个重要原因就是想解释SunJVM的效率和资源耗费问题。当然,GNUJava编译/解释器并不需要考虑太多复杂的平台,他们只需要专注于Linux和衍生自Unix System V的操作系统,对于开发人员来说,离开了Windows,一切都会变得简单起来。在这里,让我们看看GNUgij是如何解释Selector.open()Selector.wakeup()的。

     

    同样,我们需要一个测试程序。在这里,为了清晰,我不会例出所有的代码,我只给出我所使用的这个程序的一些关键代码。

     

    我的这个测试程序中,和所有的Socket程序一样,下面是一个比较标准的框架,当然,这个框架应该是在一个线程中,也就是一个需要继承Runnable接口,并实现run()方法的一个类。(注意:其中的s是一个成员变量,是Selector类型,以便主线程序使用)

     

     

            //生成一个侦听端

            ServerSocketChannel ssc = ServerSocketChannel.open();

            //将侦听端设为异步方式

            ssc.configureBlocking(false);

            //生成一个信号监视器

            s = Selector.open();

            //侦听端绑定到一个端口

            ssc.socket().bind(new InetSocketAddress(port));

            //设置侦听端所选的异步信号OP_ACCEPT

            ssc.register(s,SelectionKey.OP_ACCEPT);

      

            System.out.println("echo server has been set up ......");

     

            while(true){

                int n = s.select();

                if (n == 0) { //没有指定的I/O事件发生

                   continue;

                }    

                Iterator it = s.selectedKeys().iterator();    

                while (it.hasNext()) {

                    SelectionKey key = (SelectionKey) it.next();

                    if (key.isAcceptable()) { //侦听端信号触发

                         …… …… ……

                         …… …… ……

                    }  

                    if (key.isReadable()) { //socket可读信号

                         …… …… ……

                         …… …… ……                   

                    }    

                    it.remove();

                }

             }



     

    而在主线程中,我们可以通过Selector.wakeup()来唤醒这个阻塞在select()上的线程,下面是写在主线程中的唤醒程序:

     

     

    new Thread(this).start();

    try{

        //Sleep 30 seconds

        Thread.sleep(30000);

        System.out.println("wakeup the select");

        s.wakeup();

    }catch(Exception e){

            e.printStackTrace();

    }

     

     

    这个程序在主线程中,先启动一个线程,也就是上面那个Socket线程,然后休息30秒,为的是让上面的那个线程有阻塞在select(),然后打印出一条信息,这是为了我们用strace命令查看具体的系统调用时能够快速定位。之后调用的是Selectorwakeup()方法来唤醒侦听线程。

     

    接下来,我们可以通过两种方式来编译这个程序:

    1)使用gcj或是sunjavac编译成class文件,然后使用gij解释执行。

    2)使用gcj直接编译成可执行文件。

    (无论你用那种方法,都是一样的结果,本文使用第二种方法,关于gcj的编译方法,请参看我的《GDB调试Java程序》)

     

    编译成可执行文件后,执行程序时,使用lsof命令,我们可以看到没有任何pipe的建立。可见GNU的解释更为的节省资源。而对于一个UnixC程序员来说,这意味着如果要唤醒select()只能使用pthread_kill()来发送一个信号了。下面就让我们使用strace命令来验证这个想法。

     

    下图是使用strace命令来跟踪整个程序运行时的系统调用,我们利用我们的输出的“wakeup the select”字符串快速的找到了wakeup的实际系统调用。

     

     

    果然,我们可可以看到,tgkill(5829, 5831, SIGHUP)这个系统调用,第一个参数是“源线程id”,第二个参数是“目的线程id”,第三个参数是“信号SIGHUP”。通过每一行前面的线程号我们可以看到紧接着tgkill后面的5831线程的“… select resumed”字样。

     

    可见,GNU的确是使用最为传统的pthread_killkill系统调用向阻塞线程发信号的方法来实现Selector.wakeup()的,这也证明了GNUJava编译/解释器是不会消耗系统文件描述符的。而我们也终于看到了回归经典的Java实现机制。

     

    欢迎使用MSN和邮件和我联系:haoel@hotmail.com

     

    (转载时请注明作者和出处。未经许可,请勿用于商业用途)

    更多文章请访问我的Blog: http://blog.csdn.net/haoel

     

    发表于 @ 2008年05月04日 10:55:11|评论(loading...)|编辑

    旧一篇: 用GDB 调试Java程序

    评论

    #dadatech 发表于2008-05-04 12:01:56  IP: 222.128.58.*
    如果是我实现的话,为什么不直接在waitup方法中才建立一个到lookup的连接,然后就释放此连接呢?
    这样做既不浪费资源(不像SUN的JVM从头到尾都保持一个PIPE/Conenction)。有可以不限制在linux上。

    #dadatech 发表于2008-05-04 12:02:40  IP: 222.128.58.*
    修正笔误:
    lookup ---> loopback
    #文件夹 发表于2008-05-04 18:08:48  IP: 125.74.72.*
    强烈向大家推荐一款管理您的收藏夹的工具,http://praado.vicp.net, 无论您在任何一台电脑上,您都能轻松访问您的收藏夹(收藏您曾经浏览并有价值的一些网页链接),而且支持多级管理和模糊搜索,大大方便了在信息爆炸时代对有用信息的有效管理。试一试,您就知道!http://praado.vicp.net
    #untitled 发表于2008-05-04 21:51:36  IP: 60.242.184.*
    1. GNU的之所以要重做一个Java的编译和解释器, 不 是 因 为 想解释Sun的JVM的效率和资源耗费问题。建议你先google一下。

    2. 关于NIO在各种OS上的实现策略和缺点,很多技术网站早已做过研究讨论。说了半天,在windows上,你有不使用 loopback connection 的方法么?
    2008-05-05 12:03:37作者回复
    对于第一个评论,我所说的GNU要解释的性能问题只是其中之一,GNU的为什么要做一个全新的Java解释器我在我的另一篇文章《用GDB调试Java》中有全面的描述。<br /><br />对于第二个评论,本文和其上下篇讨论的是实现细节。说了半天,只是想告诉大家底层细节,而不是要解决什么,也不是要改进什么。只是在比较的方式下描述实现细节而已。
    #liigo 发表于2008-05-05 13:12:27  IP: 221.201.64.*
    GCJ的出现也许是个错误, 至少SUN这么认为.
    #huliai 发表于2008-05-05 16:19:42  IP: 123.55.207.*
    学习啦,不知道跟游戏有什么关系
    #dadatech 发表于2008-05-05 16:46:13  IP: 222.128.58.*
    对于“说了半天,在windows上,你有不使用 loopback connection 的方法么?”对于这个问题我自己实现的时候我的选择是:
    不是一直保持一个loopback connection,而是需要的时候才发起一个loopback connection。我能想到的需要的时候就只有一个,就是程序结束了,要让select“优雅”的关闭。为了一个只在整个程序生命周期使用不到万分之1的功能花费一个链接资源的话不是很值得。

    不过,我这个实现有个缺陷:
    就是服务很繁忙的时候,要发起一个loopback可能比较困难,仅此而已~
    #Untitled 发表于2008-05-13 19:01:10  IP: 60.242.184.*
    首先看
    "就是服务很繁忙的时候,要发起一个loopback可能比较困难,仅此而已~"

    会用selector的情况一般都是server (不是?), 花费一个‘本地’连接可以提高性能的话是不容置疑会考虑的。(如果是servlet server, 甚至还会有precompile jsp 的选项). 由于这个连接是实现里面的,所以不能直接提供选项在API里 (如果硬要这么做,可以参考JAXP实现)。

    然后是性能。
    刚刚看了《用GDB调试Java》,里面提到 GCJ vs JDK1.3.1 我一直想问,你自己有没有做过测试? 自从JDK1.4 开始,JVM的速度就突飞猛进,推荐你看一下 (http://www.shudo.net/jit/perf/)

    GCJ的初衷之一的确是提高性能,主要通过把bytecode预先compile成机器语言的手段。值得注意它不是为了证明Sun / IBM的JVM 没效率, 而是说用舍弃 跨平台特性 来减少运行的一道步骤 也可以换取 “更好"的性能。

    听起来的确不错,然而它却忘说Java的运行方法和C不是等同的,比如自动内存管理 (内存分配 和 GC 都是JVM里最尖端的部件)是影响Java性能的关键之一。因为这个原因,不管最后运行的是不是bytecode, GNU都多少必须自己从新实现一遍JVM里的关键部件。

    Sun和IBM都是JVM世界的佼佼者,各有所长 (比如IBM的reflection性能要比Sun的好)。除了启动速度 (第一次半秒钟?)以外,多年来高度优化的JIT已经向C 接近了, precompilation 体现不出优势。

    “传统的java还有一个比较扯的问题,就是布署起来太麻烦了,需要有N个jar文件,而不是一个可执行文件。并且,Java需要一个很肥大的运行环境。另外,在java和c/c++之间的调用慢得令人受不了。”

    如果需要,N个jar可以合并成一个。至于问为什么分成N个是个更扯的问题,难道连资源如何管理共享都要问?需要一个可执行文件的话,很多第三方软件都可以满足需要。

    J2EE 应用可以编译成可执行文件么?

    "在java和c/c++之间的调用真的慢得令人受不了"

    更是莫名其妙,NIO就是调用C的,讨论到现在有人觉得慢的受不了了么?

    不觉得这些问题只有Java世界的初学者才会问?
    2008-05-14 13:44:26作者回复
    呵呵,激怒了一个Java的Fans。JIT和GCJ的效率问题的争论由来已久了,争论不是我这些文章的意义。我只是想说明一下Java的某些底层机制,因为做为一个Java程序员来说,虚拟机让Java程序员并不明白实际上的系统调用。如果这位朋友觉得我的这些文章是小儿科的话,那就一笑了之好了,不必在这里冷嘲热讽,这只能说明技术人员的浮燥和自负。
    #Untitled 发表于2008-05-19 18:47:30  IP: 60.242.184.*
    你可能很少看国外的技术论坛,所以文章里常常带着不明确的quote和自以为是的主观性。如果你真的对NIO的这个问题感兴趣,并且想象“虚拟机让Java程序员并不明白实际上的系统调用” ... 推荐你一个4年前的网页:

    http://forum.java.sun.com/thread.jspa?threadID=579675&messageID=2940535
    2008-05-19 21:38:22作者回复
    呵呵,这个贴子在写这篇文章关就看到过了。我估计你可能看不太懂这个贴子的主要思想。其正好证明了因为Windows并不支持真正的pipe,所以只好使用了TCP loopback链接。这是4年前的贴子(其中的某些回贴人使用的也是并不确定的语气),我相信当时很多Java程序都是从C++过来的所以他们对系统API非常熟悉,但今天的Java程序员很多是直接学Java的,所以,在虚拟机上面,所有的API都被屏蔽了,导致没有系统编程经验的人并不真正熟悉实际的select的机制,也并不明白select中的死循环是如何退出的,也更不会明白为什么Windows要自创TCP链接。而GNU的gcj/gij不但没有TCP链接,连pipe都没有,这对于非C/C++出身的Java程序员来说,更是一头雾水了。
    #Untitled 发表于2008-05-22 10:28:12  IP: 218.185.53.*
    回复者(就一个,不是某些)不但清楚(不确定的语气?)地解释了windows下socket本身不直接支持pipe, 只好用另外方法解决,还研究过selector的C实现。因为你在用中文版ubuntu, 所以没看懂也不奇怪。

    看得出来,即使你偶尔搜索到国外网站, 也只局限于偶尔的浏览一下.因为你居然不知道在国外,大部分非 .net/VB developer都会用linux, 很多大学里根本不存在windows (我读过的就是, 第一个写的程序就是用C在debian里运行)。

    你的所谓nio研究更多的只是展示你对 linux 的一些运用,比如这篇无非就是显示一下signal&thread(linux下基本常识之一)的一点知识。

    建议,如果你想敢向linux community展示你的技术,就写一些open source software。我的计算机是双系统(因为工作原因),切换到linux就可以直接compile了。

    2008-05-22 14:14:30作者回复
    你的说这些和我的文章有什么关系?我写我的东西,我介绍我想要介绍的东西,觉得有用当然不错,觉得没有用或小儿科那就走开就好了在知识的海洋里,有的人从事实干,有的人从事理论,有的人从事评论,有的人的从事报道,有的人写写日记心得,有的的写写读后感……每个人都有自己闪光的一面,为什么非要去写open source?或是要把自己的电脑装成双操作系统?才觉得很牛呢?我把我所知道的东西跟大大家共享,我把我的心得和大家分享,不知道有哪里让你觉得不爽?你相方设法地用扁低对方的手段来显示自己的高大的作法,实在让人感到恶心。
    #poet 发表于2008-05-26 18:18:29  IP: 61.183.225.*
    > Sun和IBM都是JVM世界的佼佼者,各有所长 (比如IBM的reflection性能要比Sun的好)。除了启动速度 (第一次半秒钟?)以外,多年来高度优化的JIT已经向C 接近了, precompilation 体现不出优势。

    如果你只搞过服务端应用,得出这种结论很正常,服务器的特点就是硬件不值钱,我可以上很多内存,很多CPU。。。但是既然Java的口号是跨平台,你就不能只考虑服务端应用。

    在嵌入式应用里面,如果一个编译的程序花5M内存,JIT要花26M内存的话,谁快谁慢是想都不用想的。“利用内存空间换取执行效率”的做法,并非是放之四海而皆准,相反至少80%以上的嵌入式设备都存在内存非常不足的问题,不允许任何形式的挥霍。

    某某人称自己读过的大学里如何如何,可不要忘了这是在中国的论坛上。发达国家的Java普及程度高我很清楚,毕竟我也是国外毕业,但是拿着一些没边的比较说明java的性能跟C看齐就相当无知了。——CPU足够快之后,程序性能是没什么差别,但是世界上的程序并非都是在x86 PC上执行的。比他们慢十倍甚至百倍的CPU比比皆是。再去测测看?

    #zhouxz1026 发表于2008-05-29 14:27:25  IP: 125.106.100.*
    不得不说这个文章很有水平,也说明了作者的涵养。学习了!
    蜂胶
    蜂蜜
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © 陈皓