Java-JVM_01_前端编译器

1.编译器

1.1.编译期分类

一个*.java文件总体要经过编译期和运行期,会涉及到两类编译期:

编译期编译:一般表示*.java->*.class(包含字节码)的过程 — 也叫前端编译。

运行期编译:一般表示*.class->机器码的过程 — 也叫后端编译。

1.2.编译器分类

■前端编译器

●作用:把*.java->*.class,以供加载器进行类型加载,并在在编译期优化程序编码。

●种类:SunJavac、Eclipse的JDT。

■后端编译器(JIT编译器)

●作用:把*.class->机器码,以供解释器解释执行,并在在运行期优化程序运行。Just In Time Compiler,专指在VM运行的编译器。

●种类:HotSpot VM中的C1、C2编译器。

■静态提前编译器(AOT编译器)

●作用:直接把*.java文件编译成本地机器代码。Ahead Of Time Compiler。

2.Javac

Javac是Sun公司,使用Java语言开发的Java Compiler,只针对*.java文件,将其编译成*.class文件。Javac很具有代表性,所以以Javac为例讲解整个前端编译过程。

:其他语言编写的源代码文件,也是可以由特定的编译器编译成*.class文件的。如JRuby、Groovy语言编写的源代码文件,经编译器编译成*.class文件后,亦可以在JVM上运行。

2.1.Javac编译的逻辑过程

2.1.1.Javac编译的逻辑源码

由于Javac由Java编写,所以可以通过查看它的源码了解其编译*.java的逻辑过程。从Javac的源码中可梳理出编译的过程,大致分为三大过程:解析与填充符号表;注解器的注解处理;语义分析和字节码生成。整个过程主要由其API中的compile()和compile2()方法来完成。逻辑源码如下:

2.1.2.Javac编译的逻辑图

注解处理------又返回到------>解析与填充符号表的解释:因为在注解处理过程中,有可能对语法树进行了修改,所以要回到“解析与填充符号表”过程重新处理,直到没有对语法树的修改为止。

2.2.Javac编译的详细过程

2.2.1.解析与填充符号表过程

2.2.1.1.解析(词法、语法分析)阶段

解析包括:词法分析和语法分析,在parseFiles()方法中完成。

■词法分析:将源代码中的字符流转变为标记(Token)集合的过程。

■语法分析:根据Token序列构造*.java抽象语法树的过程。

●抽象语法树(SAT-Abstract Syntax Tree):用来描述程序代码语法结构的树形表达方式;表示一个源代码文件结构正确的抽象。

●抽象语法树的结构视图如下:

:经过解析过程后,编译器就基本不再对*.java文件进行操作了,后续的操作都是以其抽象语法树为基础进行的。

2.2.1.2.填充符号表阶段

完成词法、语法分析后,接着就是填充符号表,在enterTrees()方法中完成。

■符号表:由一组符号地址和符号信息构成的表格。

■作用:该表可用于编译期的不同阶段。

●语义分析阶段:语义检查和中间代码生成。

●字节码生成阶段:对符号名进行地址分配时,是地址分配的依据。

比如:在该阶段默认构造方法的添加。

如果代码中没有提供任何构造方法,那么在该阶段编译器会添加一个无参的、访问类型(public、protected或private)和当前类一致的,默认构造方法。

2.2.2.注解器处理注解过程

注解器的初始化过程在initProcessAnnotations()方法中完成,执行过程在

processAnnotations()方法中完成。

■注解(Annotations):与Java代码一样,是在运行期间发挥作用的。

■在处理注解过程中,如果注解对抽象语法树进行了修改(比如重新添加了一些代码等修改),那么编译器会回到“解析与填充符号表”阶段。比如:在代码中使用注解处理器的情  况。

●注解被处理之前

 1 public @Data class LombokPojoDemo {
 2     private String name;
 3 }
 4
 5 // from project lombok
 6 @Target(ElementType.TYPE)
 7 @Retention(RetentionPolicy.SOURCE)
 8 public @interface Data {
 9     String staticConstructor()
10     default "";
11 }

●注解被处理之后

2.2.3.语义分析与字节码生成过程

2.2.3.1.语义分析阶段

语法分析之后,编译器获得了一个*.java的抽象语法树,该语法树表示这个*.java的结构是正确的。但并不知道,其中的源码在逻辑上是否也是正确的。语义分析的任务就是对结构上正确的*.java进行逻辑上的检查(上下文有关性质的审查:如进行类型审查),具体的检查操作是在抽象语法树上完成的。比如:

后续出现的三种运算表达式,在Java语言的结构上都是正确的,但后两个表达式并不符合Java语言的逻辑,即在语义上是不正确的(注:在C语言中后两个表达式是符合C语言语义的)。

Javac的编译过程中,语义分析包括:标注检查;数据流与控制流分析。

■标注检查

●标注检查在attribute()方法中完成。

●标注检查的内容包括:变量使用前是否已被声明;变量与赋值之间的数据类型是否匹配;常量折叠等。

  ●比如常量折叠:对于在编译期间就可确定值的常量,如果有“+”操作符,则无需等到程序运行起来后再进行“+”操作,而是在编译的语义分析阶段,编译器直接就确定常量“+”之       后的结果值。

  ◎比如数值类型常量的折叠:int a = 1 + 2;在语法树上仍能看到字面量“1”、“2”和操作符“+”,但经过标注检查中的常量折叠后,它们会被折叠为字面量“3”,标注在语法树            上。

:由于在编译期进行了常量折叠,所以代码中定义的int a = 1 + 2;,并不会比定义a = 3;增加CPU的运算量。

◎比如String类型常量的折叠:

1 public class Test26{
2     public static void main(String[] args){
3         String a = "a9";
4         String b = "a" + 9;
5         System.out.println(a == b);
6     }
7 }                

运行结果是:true

编译后的Java Class文件如下:

说明:查看编译后的Java Class文件中的constant_pool,会发现其中只有一个字符串字面值”a9”,即是说"a" + 9在编译阶段就已经被优化折叠成了”a9”。

◎String类型的变量就没有所谓的折叠:

1 public class Test26{
2     public static void main(String[] args){
3     String a = "qinfen";
4     String b = "qin";
5     String c = b + “fen”;
6     System.out.println(a == c);
7     }
8 }

运行结果是:false

说明:“+”操作中,由于有字符串引用b的存在,而编译器是无法在编译期间确定b的值的,所以就无法进行常量折叠。“+”操作是在程序运行期间使用StringBuilder的append()方法进行替换的。

作为对比:

1 public class Test26{
2     public static void main(String[] args){
3         String a = "qinfen";
4         final String b = "qin";
5         String c = b + “fen”;
6         System.out.println(a == c);
7     }
8 }            

运行结果是:true

编译后的Java Class文件如下:

说明:对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的b + “fen”和"qin" + "fen"效果是一样的。

■数据流与控制流分析

●数据流以控制流分析在flow()方法中完成。

●它是对程序上下文逻辑更进一步的验证,包括:局部变量在使用前是否已被赋值;方法的每条路径是否都有返回值;是否所有的受查异常都被正确处理了;检查final修       饰的变量不被重复赋值等。

●编译期的数据流及控制流分析和类加载时的数据流及控制流分析的目的基本上一致。

2.2.3.2.解(除)语法糖阶段

语法糖:指在计算机语言添加的某种语法,对语言的功能没有影响,但是更方便程序员使用。由desugar()方法完成。

■语法糖有:泛型、变长变量、自动装箱拆箱、断言(assertion)、foreach循环、enum类型的switch、String类型的switch(Java 7)、具名内部类/匿名内部类/类字面量等   等。

■解语法糖:JVM运行时不支持语法糖,所以它们在编译阶段会被还原回简单的基础语法结构。

  ●比如:泛型

◎泛型:本质就是参数化类型,把操作的数据类型指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别成为泛型类、泛型接口和泛型方法。

◎Java语言中泛型只存在于源码中,在编译期会将其还原为原生类型。如果源码中有:ArrayList<int>和ArrayList<String>,在编译期会将它俩还原为:ArrayList和                  ArrayList的原生类型。所以,对于运行期而言ArrayList<int>和ArrayList<String>是同一个类型。

  ●比如:削除if (false) { … }形式的无用代码

满足下述所有条件的代码被认为是条件编译的无用代码,会在该阶段被清除。

◎if语句的条件表达式是Java语言规范定义的常量表达式。

◎并且常量表达式的值为false则then块为无用代码;反之则else块为无用代码。

  ●比如:泛型+自动装箱拆箱

◎类型转换前(解语法糖前)

public void desugarGenericToRawAndCheckcastDemo() {
    List<Integer> list = Arrays.asList(1, 2, 3);
    list.add(4);
    int i = list.get(0);
}

◎类型转换后(解语法糖前)

public void desugarGenericToRawAndCheckcastDemo() {
    List list = Arrays.asList(1, 2, 3);
    list.add(4);
    int i = (Integer)list.get(0);
}

◎解语法糖后

●foreach循环

◎解语法糖前

public void desugarDemo() {
    Integer[] array = {1, 2, 3};
    for (int i : array) {
        System.out.println(i);
    }
    assert array[0] == 1;
}    

◎解语法糖后

public void desugarDemo() {
    Integer[] array = {
            Integer.valueOf(1), Integer.valueOf(2),   Integer.valueOf(3)
        };
    for (Integer[] arr$ = array, len$ = arr$.length, i$ = 0;i$ < len$;     ++i$) {
        int i = arr$[i$].intValue();
        {
            System.out.println(i);
       }
    }
if (!$assertionsDisabled && !(array[0].intValue() == 1))throw new AssertionError();
}            

2.2.3.3.生成阶段(字节码->*.class文件)

该阶段的主要任务就是由编译器生成Java字节码,但在Java字节码生成之前,编译器

会在抽象语法树上进行一些代码的生成添加和转换操作。

■代码的添加和转换

  ●类型初始化方法<clinit>()和类实例化方法<init>()的生成添加

◎类型初始化方法<clinit>()的生成添加

在该阶段,如果源代码中含有:静态变量的赋值语句;或static{}代码块,编译器则会创建1个该类(或接口)的初始化方法<clinit>()(有且只有一个),并将静态变量       的赋值语句或static{}代码块收集到<clinit>()方法中。

注:有3种情况,使编译器在该阶段不会生成类初始化方法<clinit>():①类中没有声明静态变量,也没有static{}代码块;②类中声明了静态变量,但没有任何赋值语句;③类中仅包含编译时常量。

◎类实例化方法<init>()的生成添加

在该阶段,编译器会针对类中的每一个构造方法,对应生成一个<init>()方法。如果类中没有显式的声明任何构造方法,编译器还是会生成一个默认的无参构造方法(该构造          方法仅调用超类的无参构造方法),对应的还是会生成一个<init>()方法。

如果源代码中含有:实例变量的赋值语句;或{}代码块,编译器会将它们收集到<init>()方法中。所以构造方法对应的类实例化方法<init>()中通常会包含3种代码:①          另一个<init>()方法的调用;收集到的实例变量赋值语句或{}代码块;构造方法体中所有的代码语句。

■代码的转换(优化程序)

  ●把字符串的“+”操作,替换为StringBuffer或StringBuilder的append()操作。

  ●x++/x—在条件允许时被优化为++x/--x

最后,在完成对语法树的遍历(后序遍历)和调整之后,会把填充了所有所需信息的符号表交到com.sun.tools.javac.jvm.ClassWriter类手上,再有这个类的writeClass()方法输出字节码,生成最终的*.class文件。

时间: 2024-08-06 23:02:34

Java-JVM_01_前端编译器的相关文章

学了编译原理能否用 Java 写一个编译器或解释器?

16 个回答 默认排序? RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和解释器.其实用什么语言来实现编译器并不是最重要的部分(虽然Java也不是实现编译器最方便的语言),最初用啥语言都可以. 我在大学的时候,我们的软件工程和计算机科学的编译原理课的作业好像都是可以用Java来写的.反正我印象中我给这两门课写的作业都是用的Java. ===================

[Java Performance] JIT编译器简介

使用JIT(Just-In-Time)编译器 JIT编译器概览 JIT编译器是JVM的核心.它对于程序性能的影响最大. CPU只能执行汇编代码或者二进制代码,所有程序都需要被翻译成它们,然后才能被CPU执行. C++以及Fortran这类编译型语言都会通过一个静态的编译器将程序编译成CPU相关的二进制代码. PHP以及Perl这列语言则是解释型语言,只需要安装正确的解释器,它们就能运行在任何CPU之上.当程序被执行的时候,程序代码会被逐行解释并执行. 编译型语言的优缺点: 速度快:因为在编译的时

Web的大趋势:Java+大前端

前后端分离,是目前Web开发的主流模式也是趋势.而Java无疑是后端开发的王者,PHP和.NET目前仍处于水深火热之中,更像是在夹缝中求生存.而大前端,强势崛起!Java+大前端这一强强组合,面对其他Web领域的竞争者,可以将其按在地上使劲摩擦(没别的歧视的意思,只是想表达这样的组合,强大得可怕). PHP要生存,怎么办?不断的往H5移动端靠,不断的往所谓的全栈靠,让其作为一些前端开发人员的全栈补充技能,甚至许多PHP程序员被要求掌握Python.想来也觉得搞笑,专业的人做专业的事,掌握了nod

P4语言环境安装(一)前端编译器p4c、后端编译器p4c-bm2-ss

这个P4安装环境是在2020-2-8安装的,安装环境卡了我好几天,把遇到的问题记录下来,有需要的同学可以参考一下,要是说错了或者有问题的话,评论或mail:[email protected]联系我都可以. P4语言组织官网:https://p4.org/ 本文安装代码就是从官网引导的p4language上下载的. 介绍 我看P4是刚看了三四十小时,大都用在安装环境了,觉得它就是一个控制修改数据流的语言,提供一个标准的结构,方便用户对控制平面和数据平面进行修改. 我理解的P4开发流程就是四步 程序

Java Web前端到后台常用框架介绍

转自: http://blog.csdn.net/u013142781/article/details/50922010 一.SpringMVC http://blog.csdn.net/evankaka/article/details/45501811 Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spri

JAVA解决前端跨域问题。

什么是跨域? 通俗来说,跨域按照我自己的想法来理解,是不同的域名之间的访问,就是跨域.不同浏览器,在对js文件进行解析是不同的,浏览器会默认阻止,所以 现在我来说下用java代码解决前端跨域问题. 用java代码解决前端跨域问题? 找到WEB-INF下面的web.xml文件,输入下面代码,在web.xml文件下面: 1 <!-- 解决跨域访问的问题 --> 2 <filter> 3 <filter-name>cors</filter-name> 4 <

近期的Java项目(前端)

1.项目名:栏目选择框(前端) 2.项目源码--话不多说,直接附上源码吧,感受会更直观一些 package java项目; import java.awt.BorderLayout; import java.awt.Container; import java.awt.FlowLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.

java web前端easyui(layout+tree+双tabs)布局+树+2个选项卡tabs

1.列出要实现的样式: 2.实现的代码: 分三大部分: 1):页面主体部分:mian.vm <html> <head> <title>Ks UI</title> #parse("ui:include") <style> body{padding:0;margin:0} </style> <script> $(document).ready(function(){ var tabs_content = $

java虚拟机和编译器版本不一致问题

错误代码: Exception in thread "main" java.lang.UnsupportedClassVersionError: VarDemo : Unsupported major.minor version 51.0 at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(Unknown Source) at java.lang.Cl