本文将是JVM 性能优化系列的第二篇文章(第一篇:传送门),Java 编译器将是本文讨论的核心内容。
本文中,作者(Eva Andreasson)首先介绍了不同种类的编译器,并对客户端编译,服务器端编译器和多层编译的运行性能进行了对比。然后,在文章的最后介绍了几种常见的JVM优化方法,如死代码消除,代码嵌入以及循环体优化。
Java最引以为豪的特性“平台独立性”正是源于Java编译器。软件开发人员尽其所能写出最好的java应用程序,紧接着后台运行的编译器产生高效的基于目标平台的可执行代码。不同的编译器适用于不同的应用需求,因而也就产生不同的优化结果。因此,如果你能更好的理解编译器的工作原理、了解更多种类的编译器,那么你就能更好的优化你的Java程序。
本篇文章突出强调和解释了各种Java虚拟机编译器之间的不同。同时,我也会探讨一些及时编译器(JIT)常用的优化方案。
什么是编译器?
简单来说,编译器就是以某种编程语言程序作为输入,然后以另一种可执行语言程序作为输出。Javac是最常见的一种编译器。它存在于所有的JDK里面。Javac 以java代码作为输出,将其转换成JVM可执行的代码—字节码。这些字节码存储在以.class结尾的文件中,并在java程序启动时装载到java运行时环境。
字节码并不能直接被CPU读取,它还需要被翻译成当前平台所能理解的机器指令语言。JVM中还有另一个编译器负责将字节码翻译成目标平台可执行的指令。一些JVM编译器需要经过几个等级的字节码代码阶段。例如,一个编译器在将字节码翻译成机器指令之前可能还需要经历几种不同形式的中间阶段。
从平台不可知论的角度出发,我们希望我们的代码能够尽可能的与平台无关。
为了达到这个目的,我们在最后一个等级的翻译—从最低的字节码表示到真正的机器代码—才真正将可执行代码与一个特定平台的体系结构绑定。从最高的等级来划分,我们可以将编译器分为静态编译器和动态编译器。 我们可以根据我们的目标执行环境、我们渴望的优化结果、以及我们需要满足的资源限制条件来选择合适的编译器。在上一篇文章中我们简单的讨论了一下静态编译器和动态编译器,在接下来的部分我们将更加深入的解释它们。
静态编译 VS 动态编译
我们前面提到的javac就是一个静态编译的例子。对于静态编译器,输入代码被解释一次,输出即为程序将来被执行的形式。除非你更新源代码并(通过编译器)重新编译,否则程序的执行结果将永远不会改变:这是因为输入是一个静态的输入并且编译器是一个静态的编译器。
通过静态编译,下面的程序:
将会转换成类似下面的字节码:
JVM种类以及Java的平台独立性
所有JVM的实现都有一个共同的特点就是将字节码编译成机器指令。一些JVM在加载应用程序时对代码进行解释,并通过性能计数器来找出“热”代码;另一些JVM则通过编译来实现。编译的主要问题是集中需要大量的资源,但是它也能带来更好的性能优化。
如果你是一个java新手,JVM的错综复杂肯定会搞得你晕头转向。但好消息是你并不需要将它搞得特别清楚!JVM将管理代码的编译和优化,你并不需要为机器指令以及采取什么样的方式写代码才能最佳的匹配程序运行平台的体系结构而操心。
从java字节码到可执行
一旦将你的java代码编译成字节码,接下来的一步就是将字节码指令翻译成机器代码。这一步可以通过解释器来实现,也可以通过编译器来实现。
解释
解释是编译字节码最简单的方式。解释器以查表的形式找到每条字节码指令对应的硬件指令,然后将它发送给CPU执行。
你可以将解释器想象成查字典:每一个特定的单词(字节码指令),都有一个具体的翻译(机器代码指令)与之对应。因为解释器每读一条指令就会马上执行该指令,所以该方式无法对一组指令集进行优化。同时每调用一个字节码都要马上对其进行解释,因此解释器运行速度是相当慢得。解释器以一种非常准确的方式来执行代码,但是由于没有对输出的指令集进行优化,因此它对目标平台的处理器来说可能不是最优的结果。
编译
编译器则是将所有将要执行的代码全部装载到运行时。这样当它翻译字节码时,就可以参考全部或部分的运行时上下文。它做出的决定都是基于对代码图分析的结果。如比较不同的执行分支以及参考运行时上下文数据。
在将字节码序列被翻译成机器代码指令集后,就可以基于这个机器代码指令集进行优化。优化过的指令集存储在一个叫代码缓冲区的结构中。当再次执行这些字节码时,就可以直接从这个代码缓冲区中取得优化过的代码并执行。在有些情况下编译器并不使用优化器来进行代码优化,而是使用一种新的优化序列—“性能计数”。
使用代码缓存器的优点是结果集指令可以被立即执行而不再需要重新解释或编译!
这可以大大的降低执行时间,尤其是对一个方法被多次调用的java应用程序。
优化
通过动态编译的引入,我们就有机会来插入性能计数器。例如,编译器插入性能计数器,每次字节码块(对应某个具体的方法)被调用时对应的计数器就加一。编译器通过这些计数器找到“热块”,从而就能确定哪些代码块的优化能对应用程序带来最大的性能提升。运行时性能分析数据能够帮助编译器在联机状态下得到更多的优化决策,从而更进一步提升代码执行效率。因为得到越多越精确的代码性能分析数据,我们就可以找到更多的可优化点从而做出更好的优化决定,例如:怎样更好的序列话指令、是否用更有效率的指令集来替代原有指令集,以及是否消除冗余的操作等。
例如
考虑下面的java代码
bipush 7
iadd
ireturn
本文地址:https://www.stayed.cn/item/15536
转载请注明出处。
本站部分内容来源于网络,如侵犯到您的权益,请 联系我