亚太娱乐平台报道JVM性能优化系列-(5) 早期编译优化_亚太娱乐平台官网资讯

来自:博客园精华区 2020-02-25

5. 早期编译优化

早起编译优化主要指编译期进行的优化。

java的编译期可能指的以下三种:

    前端编译器 :将 .java文件变成 .class文件,例如Sun的Javac、Eclipse JDT中的增量式编译器(ECJ) JIT编译器(Just In Time Compiler) :将字节码变成机器码,例如HotSpot VM的C1、C2编译器 AOT编译器(Ahead Of Time Compiler) :直接把*.java文件编译成本地机器码,例如GNU Compiler for the Java(GCJ)、Excelsior JET

本文中涉及到的编译器都仅限于第一类,第二类编译器跟java语言的联系不大。javac这类编译器对代码的运行效率几乎没有任何优化措施,但javac做了许多针对java语言代码进程的优化措施来改善程序员的编码风格和提高编码效率,java许多的语法特性都是靠编译器的语法糖来实现的。

5.1 javac编译器工作流程

Sun javac编译器的编译进程能够分为3个进程:

解析与填充符号表进程 插入式注解处置器的注解处置进程 分析与字节码生成进程

1. 解析与填充符号表

解析程序包括了经典程序编译原理中的词法分析与语法分析两个进程。

词法、语法分析:词法分析是将源代码的字符流改变为标志(Token)集合,单个字符是程序编写进程的最小元素,而标志则是编译进程的最小元素,关键字、变量名、字面量、运算符都能够成为标志

语法分析是依据Token序列构造抽象语法树的进程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代替着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都能够是一个语法结构。

填充符号表:符号表(Symbol Table)是由一组符号地点和符号信息构成的表格,能够想象成K-V的形式。符号表中所登记的信息在编译的差别阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检讨和发生中间代码。在目的代码生成阶段,当对符号名进行地点分配时,符号表是地点分配的依据

2. 注解处置器

注解处置器是用于提供对注解的撑腰,能够将其看成一组编译器的插件。

3. 语义分析与字节码生成

语法分析后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是相符逻辑的。

这局部主要分如下几步,完成语义分析与字节码生成:

    标注检讨

标注检讨检讨的内容包括变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。在标注检讨中,还有一个重要的动作称为常量折叠,这使得a=1+2比起a=3不会增加任何运算量

    数据及操纵流分析

数据及操纵流分析是对程序上下文逻辑更进一步的验证,能够检讨出诸如程序局部变量在使用前是否赋值、方法的每条路径是否都有返回值、是否一切的受查异常都被正确处置等

    解语法糖

语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但方便使用。java在华克娱乐 娱乐编程语言中属于低糖语言,java中的主要语法糖包括泛型、可变参数、主动装箱/拆箱等,虚拟机运行时不撑腰这些语法,它们在编译阶段还原回简单的基础语法结构,这个进程称为解语法糖

    字节码生成

字节码生成阶段不但仅时把前面各个程序所生成的信息(语法树、符号表)转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作

5.2 Java语法糖

语法糖主要是为了方便程序员的代码开拓,这些语法糖并不会提供实质性的功能改进,但是他们能提高效率。

以下介绍了Java中常用的语法糖。

泛型与类型擦除

Java中的参数化类型只在源码中存在,在编译后的字节码中,已经被替换为原来的原生类型了,并且在相应的地方插入了强制转换代码。应付运行期的Java 语言来说,ArrayList 和ArrayList 就是同一个类。所以说泛型技术实际上就是 Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。

以下两个方法,在编译时,由于类型擦除,变成了一样的原生类型List ,因此方法的特征签名变得一致,导致无法编译。

void method(List<Integer> list);
void method (List<String> list);

但是如果两者的返回值纷歧致,在JDK1.6中则能够编译通过,并不是因为返回值差别,所以重载胜利。只是因为加入返回值后,两个方法的字节码特征签名纷歧样了,所以能够共存。但是在JDK1.7和1.8中,依旧无法通过,会报两个方法在类型擦除后具有相同的特征签名。

Java代码中的方法特征签名只包含方法名称、参数顺次和参数类型,而在字节码中的特征签名还包括方法返回值及受查异常表。

方法重载要求方法具备差别的特征签名,返回值并不包含在方法的特征签名中,所以返回值不参与重载选择。但是在Class字节码文件中,只要描述符不是完全一致的两个方法就能够共存。

主动装箱和拆箱

主动装箱和拆箱实现了基本数据类型与工具数据类型之间的隐式转换。

public void autobox() {
    Integer one = 1;
    if (one == 1) {
        System.out.println(one);
    }
}

下面对主动装箱和主动拆箱进行详细介绍:

主动装箱就是Java主动将原始类型值转换成对应的工具,比如将int的变量转换成Integer工具,这个进程叫做装箱,反之将Integer工具转换成int类型值,这个进程叫做拆箱。因为这里的装箱和拆箱是主动进行的非人为转换,所以就称作为主动装箱和拆箱。原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。

何时发生主动装箱和拆箱,

    赋值:
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion
    方法挪用:当我们在方法挪用时,我们能够传入原始数据值或者工具,同样编译器会帮我们进行转换。
public static Integer show(Integer iParam){
   System.out.println("autoboxing example - method invocation i: " + iParam);
   return iParam;
}

//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

主动装箱的弊端,

主动装箱有一个问题,那就是在一个循环中进行主动装箱操作的时刻,如下面的例子就会创建多余的工具,影响程序的性能。

Integer sum = 0;
 for(int i=1000; i<5000; i++){
   sum+=i;
}

主动装箱与比较:

下面程序的输出结果是什么?

public class Main {
    public static void main(String[] args) {
         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}

在解释具体的结果时,首先必须明白如下两点:

当"=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个工具,而如果其中有一个操作数是表示式(即包含算术运算)则比较的是数值(即会触发主动拆箱的进程)。 应付包装器类型,equals方法并不会进行类型转换。

下面是程序的具体输出结果:

true
false
true
true
true
false
true

注意到应付Integer和Long,Java中,会对-128到127的工具进行缓存,当创建新的工具时,如果相符这个这个范围,并且已有存在的相同值的工具,则返回这个工具,否则创建新的Integer工具。

应付上面的结果:

c==d:指向相同的缓存工具,所以返回true;

e==f:不存在缓存,是差别的工具,所以返回false;

c==(a+b):直接比较的数值,因此为true;

c.equals(a+b):比较的工具,由于存在缓存,所以两个工具一样,返回true;

g==(a+b):直接比较的数值,因此为true;

g.equals(a+b):比较工具,由于equals也不会进行类型转换,a+b为Integer,g为Long,因此为false;

g.equals(a+h):和上面纷歧样,a+h时,a会进行类型转换,转成Long,接着比较两个工具,由于Long存在缓存,所以两个工具一致,返回true。

关于equals和==:

.equals(...)
equals(Object o)
Object#equals(Object o)

遍历循环

遍历循环语句是java5的新特征之一,在遍历数组、集合方面,为开拓人员提供了极大的方便。

public void circle() {
    Integer[] array = { 1, 2, 3, 4, 5 };

    for (Integer i : array) {

    System.out.println(i);

    }
}

在编译后的版本中,代码还原成了迭代器的实现,这也是为遍历循环需要被遍历的类实现Iterable接口的原因。

变长参数

Arrays.asList(1, 2, 3, 4, 5);

条件编译

条件编译也是java语言的一种语法糖,依据布尔常量值的真假,编译器将会把分支中不可立的代码块消除掉。

public void ifdef() {if (true) {

System.out.println("true");

} else {//此处有警告--DeadCode

System.out.println("false");

}

}

对枚举和字符串的switch撑腰

public void enumStringSwitch() {

String str = "fans";

switch (str) {

case "fans":

break;case "leiwen":

break;default:

break;

}

}

try-with-resources

在try语句中定义和封闭资源 jdk7提供了try-with-resources,能够主动封闭相关的资源(只要该资源实现了AutoCloseable接口,jdk7为绝全局部资源工具都实现了这个接口)。

staticStringreadFirstLineFromFile(Stringpath)throwsIOException{

try(BufferedReaderbr=newBufferedReader(newFileReader(path))){

returnbr.readLine();
}
}

本文由『后端精进之路』原创,首发于博客 //teckee.github.io/ , 转载请注明出处

搜索『后端精进之路』关切公众号,立刻猎取最新文章和 价值2000元的BATJ精品面试课程

var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?fc03c8be482cb50421070dc544ea100c"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); (function(){ var src = document.location.protocol +'//js.passport.qihucdn.com/11.0.1.js?0cafbe109ab248eb7be06d7f99c4009f'; document.write('