性能是 Java 平台屡屡受到指责的一个方面。然而,Java 平台的巨大成功也使得对性能问题作一番严厉的调查研究颇有必要。在这个新专栏中,无畏的优化大师 Jack Shirazi 和 Kirk Pepperdine,分别是 JavaPerformanceTuning.com 的董事和 CTO,他们在整个 Internet 上推行性能大讨论,展开他们所碰到的问题并加以澄清。本月,他们来到 JavaRanch,讨论有关编译速度、异常以及堆长度调优等方面的话题。
上个月,我们在 JavaRanch 的 Big Moose Saloon 板块上花了大量的时间,以便查看 JavaRanch 的生手会提出什么样的性能方面的疑问。后来发现,大部分问题都是关于 J2SE 和开发过程的——提出的问题主要是关于 Java 语言、核心类以及如何改进他们的开发过程。
编译速度
您是否曾发现您的编译阶段很慢?是不是 javac 所花的时间太长?那么试试 Jikes 编译器吧,在创建 .class 文件时,它会加入额外的“动力”。这就是新兴的 Jikes,它拥有完整的 Java 源支持。(可能会引起 VerifyError,不支持所有的 javac 选项,字节码可能不像所说的那么好,而且性能也可能受到影响。因此,在使用之前,请务必阅读使用手册。)
所以说,在 JavaRanch 上对 Jikes 的讨论不像我们自制的广告那么直接,但是有的读者也明确指出,Jikes Java 编译器是设计用来加快编译速度的。知道这一点很有用,尤其是对于那些需要编译很多文件的项目更是如此。不过要清楚,虽然 Jikes 有助于加快开发进程,但是对于最后的编译,最好还是使用与在生产中将要使用的 JVM 一起提供的那个编译器。不同的 JVM 版本会有不同的情况,所以当使用来自不同 JVM 的编译器时就可能引发问题。
异常开销很大
是的,异常开销很大。那么,这是不是就意味着您不该使用异常?当然不是。但是,何时应该使用异常,何时又不应该使用异常呢?不幸的是,答案不是一下子就说得清的。
我们要说的是,您不必放弃已经学到的好的 try-catch 编程习惯,但是使用异常时可能会碰到麻烦,创建异常就是一个例子。当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。还记得当代码中抛出一个意料之外的异常时,您所看到的输出来的栈跟踪吗?像下面这个:
Exception in thread "main" my.corp.DidntEXPectThisException
at T.noExceptionsHere(T.java:13)
at T.main(T.java:7)
构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。运行时栈不是为有效的异常创建而设计的,而是设计用来让运行时尽可能快地运行。入栈,出栈,入栈,出栈。让这样的工作顺利完成,而没有任何不必要的延迟。但是,当需要创建一个 Exception 时,JVM 不得不说:“先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作,笑着等我拍完快照吧。”栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素,从栈顶到栈底,还有行号和一切应有的东西。假如在一个深度为20的栈中创建了异常,那么就别指望只记录顶部的几个栈元素了——您得完完整整地记录下所有20个元素。从 main 或 Thread.run (在栈底)到栈顶,记录整个栈。
因此,创建异常这一部分开销很大。从技术上讲,栈跟踪快照是在本地方法 Throwable.fillInStackTrace() 中发生的,这个方法又是从 Throwable contrUCtor 那里调用的。但是这并没有什么影响——假如您创建一个 Exception,就得付出代价。好在捕捉异常开销不大,因此可以使用 try-catch 将核心内容包起来。您也可以在方法定义中定义 throws 子句,这样对性能不会造成什么损失,例如:
public Blah myMethod(Foo x) throws SomeBarException {
....
从技术上讲,您甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是 throw 操作——尽管在没有预先创建异常的情况下就抛出异常是有点不平常。真正要花代价的是创建异常。
try {
doThings();
if (true)
throw new SomeException(); // cos my program runs too fast
}
catch(SomeException e) {
doMoreThings();
}
幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则。但是,万一您不想遵从好的编程习惯,Java 语言就会让您知道,那样做可以让您的程序运行得更快,从而鼓励您去那样做。
最大堆长度
在我们访问过的所有讨论组中,有关 JVM 堆的问题不断冒出。在 JavaRanch 上有一次讨论就是以“最大堆长度设置应该是怎样的?”这一基本问题开始的。在深入研究之前,让我们先复习一下 Java 运行时中内存治理的基础知识。