Java虚拟机JVM性能优化(二):编译器(3)
对于服务器端的部署,编译器可能需要一些时间来优化那些“热点”代码。所以服务器端的部署常常需要一个“加热”阶段。所以当对服务器端的部署进行性能测量时,务必确保你的应用程序已经达到了稳定状态!给予编译器充足的时间进行编译将会给你的应用带来很多好处。
服务器端编译器相比客户端编译器来说能够得到更多的性能调优数据,这样就可以进行更复杂的分支分析,从而找到性能更优的优化路径。拥有越多的性能分析数据就能得到更优的应用程序分析结果。当然,进行大量的性能分析也就需要更多的编译器资源。如JVM若使用C2编译器,那么它将需要使用更多的CPU周期,更大的代码缓存区等等。
多层编译
多层编译混合了客户端编译和服务器端编译。Azul第一个在他的Zing JVM中实现了多层编译。最近,这项技术已经被Oracle Java Hotspot JVM采用(Java SE7 之后)。多层编译综合了客户端和服务器端编译器的优点。客户端编译器在以下两种情况表现得比较活跃:应用启动时;当性能计数器达到较低级别的阈值时进行性能优化。客户端编译器也会插入性能计数器以及准备指令集以备接下来的高级优化—服务器端编译器—使用。多层编译是一种资源利用率很高的性能分析方式。因为它可以在低影响编译器活动时收集数据,而这些数据可以在后面更高级的优化中继续使用。这种方式与使用解释性代码分析计数器相比可以提供更多的信息。
图1所描述的是解释器、客户端编译、服务器端编译、多层编译的性能比较。X轴是执行时间(时间单位),Y轴是性能(单位时间内的操作数)
图1.编译器性能比较
相对于纯解释性代码,使用客户端编译器可以带来5到10倍的性能提升。获得性能提升的多少取决于编译器的效率、可用的优化器种类以及应用程序的设计与目标平台的吻合程度。但对应程序开发人员来讲最后一条往往可以忽略。
相对于客户端编译器,服务器端编译器往往能带来30%到50%的性能提升。在大多数情况下,性能的提升往往是以资源的损耗为代价的。
多层编译综合了两种编译器的优点。客户端编译有更短的启动时间以及可以进行快速优化;服务器端编译则可以在接下来的执行过程中进行更高级的优化操作。
一些常见的编译器优化
到目前为止,我们已经讨论了优化代码的意义以及怎样、何时JVM会进行代码优化。接下来我将以介绍一些编译器实际用到的优化方式来结束本文。JVM优化实际发生在字节码阶段(或者更底层的语言表示阶段),但是这里将使用java语言来说明这些优化方式。我们不可能在本节覆盖所有的JVM优化方式;当然啦,我希望通过这些介绍能激发你去学习数以百计的更高级的优化方式的兴趣并在编译器技术方面有所创新。
死代码消除
死代码消除,顾名思义就是消除那些永远不会被执行到的代码—即“死”代码。
如果编译器在运行过程中发现一些多余指令,它将会将这些指令从执行指令集里面移除。例如,在列表1里面,其中一个变量在对其进行赋值操作后永远不会被用到,所有在执行阶段可以完全地忽略该赋值语句。对应到字节码级别的操作即是,永远不需要将该变量值加载到寄存器中。不用加载意味着消耗更少的cpu时间,因此也就能加快代码执行,最终导致应用程序加快—如果该加载代码每秒被调用好多次,那优化效果将更明显。
列表1 用java 代码列举了一个对永远不会被使用的变量赋值的例子。
列表1. 死代码
int timeToScaleMyApp(boolean endlessOfResources){
int reArchitect =24;
int patchByClustering =15;
int useZing =2;
if(endlessOfResources)
return reArchitect + useZing;
else
return useZing;
}