startup java fast

据我所知,有不少人[url=http://www.joelonsoftware.com/items/2009/01/12.html]鄙视java[/url],认为它笨重而缓慢,笨重倒是事实,但慢其实是站不脚的,据专业网站的[url=http://shootout.alioth.debian.org/u64q/which-programming-languages-are-fastest.php]测评[/url],Java和C/C++的性能平均差距不到两倍,在某些情况下[url=http://keithlea.com/javabench/]甚至比C/C++还快[/url]。为什么Java给人的印象很慢?主要是在于java的启动速度特别慢,在我的机器上运行HelloWorld的时间需要平均0.35秒,这只是有短暂延时,但第一次启动可能需要2秒或者更长,这种进步得益于[url=http://download.oracle.com/javase/1.5.0/docs/guide/vm/class-data-sharing.html]Class Data Sharing[/url]技术。一个python版的hello程序大概需要0.05秒,这可以说是“立即”。一个C程序需要5毫秒,仍然是“立即”,对人来说,python和c一样快,java就明显要慢多了。运行一个helloworld的scala脚本就更慢了,最快也要2.5秒,第一次启动时要7秒。总的来说,Java的慢主要体现在启动上,对于一个HelloWorld程序,98%以上的时间花在启动上,真正执行时间只需要4毫秒。

要提高Java的启动速度,一种方法就是让jvm常驻内存,这样就不用每次启动时都去加载一次jvm了,我不知道该怎么做,但肯定不简单,不然的话sun(现在是oracle)早就这么做了。印象中.net的启动速度似乎要比java快,不知是否采用了这种方法,毕竟那是MS自家的产品,优化起来也容易一些。我这里要说的是另外一种解决方法,用Java实现一个服务器,在一个固定端口监听客户端请求,来执行给定的Java程序,客户端并不直接执行Java程序,只是将要执行的程序路径告诉服务器,实际服务器来执行,这样只需要服务器一次启动jvm,客户端并不需要启动jvm,速度就会很快。客户端可以用C甚至用python来实现。fsc利用的就是这个思想,fsc类似scalac,但它比scalac快多了,fsc并不直接编译,只是连接编译deamon进程,让它去编译。

通过这种方式来执行java,最有名的解决方案是[url=http://www.martiansoftware.com/nailgun/]nailgun[/url]。但我并不喜欢它,因为它并不像我期望那样的工作,它对用户不透明。例如你在当前文件夹下有个HelloWorld的class,你执行"ng HelloWorld",结果它会说找不到HelloWorld类,因为服务器启动时HelloWorld类所在的目录并不是classpath。nailgun可以通过命令行来动态修改classpath路径,因此你可以在运行HelloWorld之前先将HelloWorld所在路径添加到nailgun服务器的classpath路径中去,添加时要指定绝对路径,"."实际上代表的是服务器启动时的路径。我觉得这些麻烦是不必要的,应当向客户端屏蔽“它实际上是在服务器端运行”这一事实,就好像它完全在本地启动jvm并执行一样。nailgun提供了一些额外的特性,包括给类起个别名,支持特定于nailgun的main启动方法,在这其中可以传递环境变量,我觉得这些都是没有必要的。

由于我不喜欢nailgun,并且我自认为找到了更好的实现方式(后来证明是错的),所以我就自己写了个[url=http://code.google.com/p/startup-java-fast/]startup-java-fast[/url]。nailgun对每个客户端是使用一个单独的线程去运行,由于线程在同一个进程空间运行,不可能完全隔离各个客户端,另外在同一个进程中意味所有客户端共享环境变量。我最初的想法是使用fork()系统调用,fork()会创建一个新的子进程,由于fork的实现方式都是[url=http://en.wikipedia.org/wiki/Copy-on-write]copy-on-write[/url],开销很低。fork的主要缺点在于java本身不提供对其的支持,我不得不借助jni来实现fork调用。我写了一个小程序来验证,证明行得通。另一个缺点就是fork只在unix下才有,在windows下没有相应的api,因为我基本不用windows,因此至少对我来说这不是问题。可是最终在实现时碰到很灵异的问题,这个问题在我之前写的验证程序中不存在。这是由于fork出来的子进程只会复制父进程中一个线程的内容,由于jvm明显使用了多线程(至少还有一个GC线程),这就导致子进程中的jvm工作不正常,出错时甚至没有任何错误提示。

由于使用进程方式失败,我只能使用线程模型,基本上走的是nailgun的路子,不同的是我致力于达到客户端的透明性。但由于线程模型的固有缺点,这个目标没有达到,我想到的主要有几点:
[list=1]
[*] 使用相对路径创建的文件是相对服务器端路径。虽然我设置了user.dir系统属性为客户端的当前路径,当由于创建文件是使用native调用,不理会user.dir系统属性。getAbsoluteFile()可以将相对路径转为绝对路径,它使用user.dir系统属性,这是用户所期望的,因此可以使用这种方式来绕过此问题。
[*] 不能再调用System的setIn/setOut/setErr/setProperties方法,因为我已经将这些值设置为ThreadLocal,这样每个客户端才会有独立的值。
[*] 调用多线程的客户端可能会有问题,尤其是当有个后台线程在运行时,这时客户主线程退出了,后台线程并不会退出,这会导致一些资源不能释放。我想到一种方法能够部分跟踪客户线程创建的其它线程,这样在主线程退出时可以回收这些线程,但我还没有实现。
[/list]

startup-java-fast可以从这里[url=http://code.google.com/p/startup-java-fast/downloads/list]下载[/url],先执行服务器,默认监听端口是0x2304:

$ java -jar sjfserver.jar


客户端我有两种实现,分别是C和python的,因为使用了对文件描述符的select,应该都不支持windows:

$ time sjf java HelloWorld
Hello World

real 0m0.017s
user 0m0.001s
sys 0m0.003s

对比python的hello程序:

$ time python hello.py
Hello World

real 0m0.045s
user 0m0.026s
sys 0m0.016s

可以看到用sjf来执行HelloWorld要比python快大约2倍。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值