Java虚拟机 程序编译

主要内容

javac编译器

java语法糖

编译期

概述

Java 语言的“编译期”可能为以下3中编译过程:

前端编译器:即编译器的前端,把.java文件转变为.class文件。

JIT编译器:把字节码转变为机器码。

AOT编译器:直接把.java转变为本地机器码。

javac编译器

10.2.1 javac的源码与调试



解析与符号填充

词法分析:将源代码的字符流转为标记(token)集合。如”int a = b+2”这句代码中包含了6个标记int、a、=、b、+、2。Javac源码中词法分析由com.sun.tools.javac.parser.Scaner类来实现。

语法分析:根据token序列来构建抽象语法树的过程,抽象语法树是一种描述程序语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构,例如包、类型、修饰符、运算符、接口、返回值甚至注释都可以是一个语法结构。

填充符号表:语法词法分析之后就是填充符号表阶段。符号表是由一组符号地址和符号信息构成的表格,其中的信息在编译的不同阶段都会用到,在语义分析中用于语义检查和产生中间代码,在目标代码生成阶段是符号名地址分配的依据。

注解处理器

注解与普通代码一样是在运行期间发挥的作用,可以把它看做编译器的插件,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理期间对语法树进行了修改,那么编译器将回到解析及填充符号表的过程重新处理。可以使用注解实现许多原本只能在编码中完成的事。

语义分析与字节码生成

语法分析后,程序获得了代码的抽象语法树,语法树能表示一个结构正确的源程序的抽象,但是不能保证符合逻辑。而语义分析的主要任务是保证结构正确的程序进行上下文有关性质的审查,如类型审查。

语义分析包括标注检查和数据及控制流分析。

1.      标注检查的内容包括变量使用前是否被声明,变量与赋值之间类型是否匹配等等。还有个特殊动作常量折叠。Int a = 1+2;折叠后会在语法树上被标注为字面量3。

2.      数据及控制流分析是对逻辑进一步验证,如局部变量使用前是否赋值、方法的每条路径是否都有返回值等。编译器与类加载时的数据及控制流分析基本一致,只是校验范围不同。如final修饰的变量,编译后与没有修饰的class文件没有任何区别。所以final修饰的变量在运行期没有任何影响,不变性仅在编译期由编译器保证。

3.      解语法糖

4.      字节码生成是javac编译过程的最后阶段,不仅仅把前面各个步骤生成的信息(语法树、符号表)转化成字节码保存到磁盘中,编译器还进行了少量代码添加和转化。比如添加默认构造方法、优化程序的实现逻辑,把string相加操作变为stringbuilder等。

Java语法糖的味道

泛型与类型擦除

两个方法有不同的返回值居然重载成功了!说明这个重载不是根据返回值来确定的,由于泛型引入可能对原有基础产生影响和新的需求,所以JCP组织对虚拟机规范做出了相应的修改,引入了新属性用于解决参数类型识别问题,包括储存一个方法在字节码层面的特征签名。擦除只是对方法的Code属性中字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射取得参数化类型的依据。

自动装箱、拆箱与遍历循环


包装类的“==”运算在没有遇到算数运算的情况下不会自动拆箱,而且equals()方法不会处理数据类型转换关系。

条件编译

运行期

Java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块运行特别频繁,就会把这些代码认定为“热点代码”,为了提高运行效率,运行时虚拟机会将这些代码编译成与本地平台相关的机器码,并进行各个层次的优化,完成这个任务的编译器叫即时编译器,也称为JIT编译器。

HotSpot虚拟机内的即时编译器

解释器与编译器

Java虚拟机大多采用解释器与编译器并存的架构,当程序需要迅速启动和执行的时候解释器可以先发挥作用,省去编译时间立即执行;当程序运行后随着时间的推移编译器逐渐发挥作用,把越来越多的代码编译成本地代码并获得更高的执行效率。

编译优化技术

以编译方式执行本地代码比解释方式更快,除去虚拟机解释执行字节码的额外消耗,JDK设计团队几乎把对代码所有的优化措施都集中在了即时编译器。

11.3.2 公共子表达式消除

如果一个表达式E已经被计算过了,并且从先前的计算到现在所有变量值都没有发生变化,那么E这次出现就成为了公共子表达式,即没有必要再次计算直接用之前的表达式结果替代E就行了。

11.3.3 数组边界检查消除

Java中访问数组元素时会进行上下界范围检查,否则会抛出运行时异常,这对于开发者来说是好事,可以防止很多溢出攻击,但是每次检查会造成性能负担。

所以只要在编译期根据数据流分析确定数组长度,并判断下标没有越界,那在执行的时候就无需判断了。

与语言相关的其他消除操作还有自动装箱消除、安全点消除、消除反射等。

11.3.4 方法内联

除了消除方法调用的成本外,更重要的是为其他优化手段建立良好基础。


方法内联的优化看起来简单,不过是把目标方法的代码“复制”到发起调用的方法之中从而避免发生真实调用而已,但如果不是编译器做了一些特别努力,按照经典编译的优化理论大多数java方法都无法内联。

对于一个虚方法(默认的实例方法就是虚方法),编译器做内联的时候无法确定使用哪个版本的方法。由此引入了”类型继承关系分析”技术来确定接口是否有多于一种实现,某个类是否有子类且子类是否抽象等信息。

在进行内联时,如果是非虚方法则直接内联;如果是虚方法且分析后只有一个版本则也可以内联。但是这种内联比较激进,如果加载了导致继承关系发生变化的新类,则退回解释状态执行,或重新进行编译。



猜你喜欢

转载自blog.csdn.net/weixin_36251021/article/details/79932226