5.3.2 升级JDK 1.6的性能变化及兼容问题

对Eclipse进行调优的第一步就是先把虚拟机的版本进行升级,希望能先从虚拟机版本身上得到一些“免费的”性能提升。

每次JDK的大版本发布时,开发商肯定都会宣称虚拟机的运行速度比上一版本有了很大的提高,这虽然是个广告性质的宣言,经常被人从升级列表或者技术白皮书中直接忽略过去,但从国内外的第三方评测数据来看,版本升级至少某些方面确实带来了一定的性能改善[1],以下是一个第三方网站对JDK 1.5、1.6、1.7三个版本做的性能评测,分别测试了以下4个用例[2]

生成500万个的字符串。

500万次ArrayList<String>数据插入,使用第一点生成的数据。

生成500万个HashMap<String,Integer>,每个键-值对通过并发线程计算,测试并发能力。

打印500万个ArrayList<String>中的值到文件,并重读回内存。

三个版本的JDK分别运行这3个用例的测试程序,测试结果如图5-4所示。

figure_0165_0064

图 5-4 JDK横向性能对比

从这4个用例的测试结果来看,JDK 1.6比JDK 1.5有大约15%的性能提升,尽管对JDK仅测试这4个用例并不能说明什么问题,需要通过测试数据来量化描述一个JDK比旧版提升了多少是很难做到非常科学和准确的(要做稍微靠谱一点的测试,可以使用SPECjvm2008[3]来完成,或者把相应版本的TCK[4]中数万个测试用例的性能数据对比一下可能更有说服力),但我还是选择相信这次“软广告”性质的测试,把JDK版本升级到1.6 Update 21。

与所有小说作者设计的故事情节一样,获得最后的胜利之前总是要经历各种各样的挫折,这次升级到JDK 1.6之后,性能有什么变化先暂且不谈,在使用几分钟之后,笔者的Eclipse就和前面几个服务端的案例一样非常“不负众望”地发生了内存溢出,如图5-5所示。

figure_0166_0065

图 5-5 Eclipse OutOfMemoryError

这次内存溢出完全出乎笔者的意料之外:决定对Eclipse做调优是因为速度慢,但开发环境一直都很稳定,至少没有出现过内存溢出的问题,而这次升级除了eclipse.ini中的JVM路径改变了之外,还未进行任何运行参数的调整,进到Eclipse主界面之后随便打开了几个文件就抛出内存溢出异常了,难道JDK 1.6 Update 21有哪个API出现了严重的泄漏问题吗?

事实上,并不是JDK 1.6出现了什么问题,根据前面章节中介绍的相关原理和工具,我们要查明这个异常的原因并且解决它一点也不困难。打开VisualVM,监视页签中的内存曲线部分如图5-6和图5-7所示。

figure_0167_0066

图 5-6 Java堆监视曲线

figure_0167_0067

图 5-7 永久代监视曲线

在Java堆中监视曲线中,“堆大小”的曲线与“使用的堆”的曲线一直都有很大的间隔距离,每当两条曲线开始有互相靠近的趋势时,“最大堆”的曲线就会快速向上转向,而“使用的堆”的曲线会向下转向。“最大堆”的曲线向上是虚拟机内部在进行堆扩容,运行参数中并没有指定最小堆(-Xms)的值与最大堆(-Xmx)相等,所以堆容量一开始并没有扩展到最大值,而是根据使用情况进行伸缩扩展。“使用的堆”的曲线向下是因为虚拟机内部触发了一次垃圾收集,一些废弃对象的空间被回收后,内存用量相应减少,从图形上看,Java堆运作是完全正常的。但永久代的监视曲线就有问题了,“PermGen大小”的曲线与“使用的PermGen”的曲线几乎完全重合在一起,这说明永久代中没有可回收的资源,所以“使用的PermGen”的曲线不会向下发展,永久代中也没有空间可以扩展,所以“PermGen大小”的曲线不能向上扩展。这次内存溢出很明显是永久代导致的内存溢出。

再注意到图5-7中永久代的最大容量:“67,108,864个字节”,也就是64MB,这恰好是JDK在未使用-XX:MaxPermSize参数明确指定永久代最大容量时的默认值,无论JDK 1.5还是JDK 1.6,这个默认值都是64MB。对于Eclipse这种规模的Java程序来说,64MB的永久代内存空间显然是不够的,溢出很正常,那为何在JDK 1.5中没有发生过溢出呢?

在VisualVM的“概述-JVM参数”页签中,分别检查使用JDK 1.5和JDK 1.6运行Eclipse时的JVM参数,发现使用JDK 1.6时,只有以下3个JVM参数,如代码清单5-5所示。

代码清单5-5 JDK 1.6的Eclipse运行期参数


-Dcom.sun.management.jmxremote

-Dosgi.requiredJavaVersion=1.5

-Xmx512m


而使用JDK 1.5运行时,就有4条JVM参数,其中多出来的一条正好就是设置永久代最大容量的-XX:MaxPermSize=256M,如代码清单5-6所示。

代码清单5-6 JDK 1.5的Eclipse运行期参数


-Dcom.sun.management.jmxremote

-Dosgi.requiredJavaVersion=1.5

-Xmx512m

-XX:MaxPermSize=256M


为什么会这样呢?笔者从Eclipse的Bug List网站[5]上找到了答案:使用JDK 1.5时之所以有永久代容量这个参数,是因为在eclipse.ini中存在“—launcher.XXMaxPermSize 256M”这项设置,当launcher——也就是Windows下的可执行程序eclipse.exe,检测到假如是Eclipse运行在Sun公司的虚拟机上的话,就会把参数值转化为-XX:MaxPermSize传递给虚拟机进程,因为三大商用虚拟机中只有Sun系列的虚拟机才有永久代的概念,也就是只有HotSpot虚拟机需要设置这个参数,JRockit虚拟机和IBM J9虚拟机都不需要设置。

在2009年4月20日,Oracle公司正式完成了对Sun公司的收购,此后无论是网页还是具体程序产品,提供商都从Sun变为了Oracle,而eclipse.exe就是根据程序提供商判断是否为Sun的虚拟机,当JDK 1.6 Update 21中java.exe、javaw.exe的“Company”属性从“Sun Microsystems Inc.”变为“Oracle Corporation”之后,Eclipse就完全不认识这个虚拟机了,因此没有把最大永久代的参数传递过去。

了解原因之后,解决方法就简单了,launcher不认识就只好由人来告诉它,即在eclipse.ini中明确指定-XX:MaxPermSize=256M这个参数就可以了。

[1]版本升级也有不少性能倒退的案例,受程序、第三方包兼容性以及中间件限制,在企业应用中升级JDK版本是一件需要慎重考虑的事情。

[2]测试用例、数据及图片来自:http://geeknizer.com/java-7-whats-new-performance-benchmark-1-5-1-6-1-7

[3]官方网站:http://www.spec.org/jvm2008/docs/UserGuide.html。

[4]TCK(Technology Compatibility Kit)是一套由一组测试用例和相应的测试工具组成的工具包,用于保证一个使用Java技术的实现能够完全遵守其适用的Java平台规范,并且符合相应的参考实现。

[5]https://bugs.eclipse.org/bugs/show_bug.cgi?id=319514。