学习《深入了解Java虚拟机》有一段时间了,大概理解了Java从源代码编译到执行出结果的过程,也能明确的知道Java是半解释性语言。在执行源代码时,先通过Javac编译器对源代码进行词法分析、语法分析、生成抽象语法树、语义分析等,这部分操作是在Java虚拟机之外进行的,而解释器在虚拟机内部,所以Java程序的编译就是半独立的实现过程。

一、了解一下javac编译的详解过程

编译过程大致上分为三步:解析与填充符号表过程、插入式注解处理器的注解处理过程、分析与字节码生成过程。

(1)词法、语法分析

  词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符都可以称为标记,如“int a = b + 2”这句代码包含了6个标记,不可拆分,分别为int、a、=、b、+、2,虽然关键字int由3个字符构成,但是它只是一个标记(Token),不可再拆分。
  语法分析是根据Token序列构成抽象语法树的过程,抽象语法树(Abstract Syntax Tree)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。

(2)符号填充表(目前这点知识我不是很理解)

  完成词法分析和语法分析后,下一步就是填充符号表的过程,符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,可以将它想象成哈希表中K-V键值对的形式(实际上符号表不一定是哈希表实现,可以是有序符号表、树状符号表、栈结构符号表等)。符号表中所登记的信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的说明是否一致)和产生中间代码。在目标代码生成阶段,当对符号表名进行地址分配时,符号表是地址分配的依据。

(3)注解处理器

  在JDK 1.5之后,Java语言提供了对注解(Annotation)的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。在JDK 1.6中提供了一组插入式注解处理器的标准API在编译期间对注解进行处理,我们可以把它看做是一组编译器的插件,在这些插件里面可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,也就是上图的回环过程。
  有了编译器注解处理器的标准API后,我们的代码才有可能干涉编译器的行为,由于语法树中的任意元素,甚至包括代码注释都可以在插件中访问到,所以通过插入式注解处理器实现的插件功在功能上有很大的发挥空间。

(4)语义分析与字节码生成

  语义分析之后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但无法表示源程序是否符合逻辑。而语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,如进行类型审查!
  javac分析过程分为标注检查以及数据及控制流分析;

  a) 标注检查

int a = 1;
boolean b = false;
char c = 2;
 
  后续可能会出现的赋值运算:

int d = a + c;
int d = b + c;
char d = a + c;
 
   后续代码中如果出现了如上3中赋值运算的话,那它们都能构成结构正确的语法树,但是只有第1种的写法在语义上是没有问题的,能够通过编译,其余两种在Java语言中是不合逻辑的,无法编译(是否符合语义逻辑必须在具体的语言与具体的上下文环境之中才有意义)。

  b) 数据及控制流分析

  数据及控制流分析是对程序上下文逻辑更近异步的验证,它可以检查出诸如程序员局部变量在使用前是否有赋值、方法的每条路径是够都有返回值、是否所有的受查异常都被正确处理了等问题。有一些校验只有在编译期或运行期才能进行!  

  c) 语法糖

  语法糖是指在计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员的使用。Java中最常用的语法糖主要是泛型、变长参数、自动装箱/拆箱、条件编译等,虚拟机不支持这些语法,他们在编译阶段还原回简单的基础语法结构(泛型的擦除、变长参数封装成数组参数、Integer自动装箱拆箱变为Integer.value()等、分支不成立的代码块清除掉)。

(4)字节码生成

  字节码生成是Javac编译过程的最后一个阶段,在Javac源码里面有com.sun.tools.javac.jvm.Gen类完成。字节码生成阶段不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化为字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。  

二、Java语法糖的味道

  语法糖虽然不会提供实质性的功能改进,但是它们或能提供效率,或能提升语法的严谨性,或能减少编码出错的机会。但是大量添加和使用“含糖”的语法,容易让程序员产生依赖,无法看清程序代码的真实面目。

(1)泛型与类型擦除

  泛型早期在Java语言中没有出现时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型转化。例如:在哈希表的存取中,1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,所以Object转型成任何对象都是有可能的。但是因为有无限可能性,许多ClassCastException的风险就会转嫁到程序的运行期间。

  泛型重载(编译不通过)

public class GenericType{
public static void method(List<String> list){
System.out.print("invoke method(List<String> list)");
} public static void method(List<Integer> list){
System.out.print("invoke method(List<Integer> list)");
}
}

  编译不通过,泛型在编译阶段进行擦除,变成原生类型List<E>,擦除 动作导致这两种方法的特征签名变得一模一样。

(2)自动装箱、拆箱与遍历循环

  源代码:

public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4);
//如果在JDK 1.7中,还有另外一种语法糖
//能让上面这句话进一步简写成List<Integer> list = [1, 2, 3, 4]
int sum = 0;
for (Integer i : list) {
sum += i;
}
System.out.println(sum);
}

  编译后的代码:

public static void main(String[] args) {
List<Integer> list = Arrays.asList(new Integer[]{
Integer.valueOf(1),
Integer.valueOf(2),
Integer.valueOf(3),
Integer.valueOf(4)
});
int sum = 0;
for (Iterator localIterator = list.iterator();localIterator.hasNext();){
int i = ((Integer) localIterator.next()).intValue();
sum += i;
}
System.out.println(sum);
}

  装箱:基本类型变为包装类型,例如:Integer n = 1;

  拆箱:包装类型变为基本类型,例如:int i = n;

  重点:包装类重新赋值会创建新的对象,因为每一个包装类型中value都被final修饰的!例如Integer类中:private final int value;

(3) 条件编译

  源代码:

 public static void main(String[] args) {
if (true) {
System.out.println("block 1");
} else {
System.out.println("block 2");
}
}

  编译后的代码:

public static void main(String[] args) {
System.out.println("block 1");
}

  只能使用条件为常量的if语句才能达到上述效果,编译器将会把分支中不成立的代码块消除掉,这一过程在编译阶段完成!

  重点理解:在前端编译器中,“优化”手段主要用于提升程序的编码效率,之所以把Javac这类将Java代码转变为字节码的编译器称做“前端编译器”,是因为它只完成了从程序到抽象语法树或中间代码的生成,而在此之后,还有一组内置于虚拟机内部的“后端编译器”完成从字节码生成本地机器码的过程,就是即时编译器或JIT编译器,这个编译器的编译速度及编译结果的优劣,是衡量虚拟机性能一个很重要的指标!

  

Javac编译器详解的更多相关文章

  1. javac命令详解(下)

    摘自http://blog.csdn.net/hudashi/article/details/7058999 javac命令详解(下)                             -ver ...

  2. javac命令详解(上)

    摘自http://blog.csdn.net/hudashi/article/details/7058998   javac命令详解(上)                             ja ...

  3. JAVAC 命令详解(转)

    本文来自:http://www.cnblogs.com/JeffChen/archive/2008/01/16/1041783.html 结构 javac [ options ] [ sourcefi ...

  4. Linux安装gcc编译器详解

    本人使用的是CentOS 6.5 64位系统,由于在安装系统的时候并没有勾选安装gcc编译器,因此需要自行安装gcc编译器. 使用yum安装gcc 对于配备了yum的Linux发行版而言,安装gcc编 ...

  5. JAVAC 命令详解

    转自:http://jeffchen.iteye.com/blog/395671 结构 javac [ options ] [ sourcefiles ] [ @files ] 参数可按任意次序排列. ...

  6. Linux——CentOS7安装gcc编译器详解

    使用yum安装gcc 使用yum命令安装还是非常easy的. yum -y install gcc gcc-c++ kernel-devel //安装gcc.c++编译器以及内核文件 手动安装gcc ...

  7. 6、javac命令详解

    javac [ options ] [ sourcefiles ] [ @files ] 参数可按任意次序排列. options 命令行选项. sourcefiles 一个或多个要编译的源文件(例如 ...

  8. C++编译器详解(三)函数调用的区别:_cdecl以及_stdcall

    1._stdcall是Pascal程序的缺省调用方式,通常用于Win32 API中,函数采用从右到左的压栈方式,自己在退出时清空堆栈.VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上&qu ...

  9. C++编译器详解(二)常见precompiling 指令介绍

    VC++编译器中一些常见precompiling 指令介绍 我们在利用vc6.0 project wizard生成的代码中,经常看到大量的precompiling指令.本文讲解了常见的这些指令的作用 ...

随机推荐

  1. linux(centos8):安装jmeter5.3

    一,jmeter的用途: Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试, 它最初被设计用于Web应用测试,但后来扩展到其他测试领域.  Apach ...

  2. 懒人福音——GitHub 热点速览 Vol.42

    作者:HelloGitHub-小鱼干 懒人福音是什么?就是省时省事,正如 Waypoint 一样,你不需要在多个平台构建代码即可部署发布应用,它允许你将应用程序构建.部署和发布生命周期定义为代码.Bi ...

  3. Linux的外部命令的执行

    查看外部命令的路径 whereis 不但能显示出外部命令的路径还能显示出帮助文档 which -a |--skip-alias whereis

  4. CSS动画菜鸡记录板

    Transition 过渡属性: (background 1s linear 0s) 缓动函数 linear,在 easings.net 可找到相应的功能 若想要多次不同执行,用逗号隔开 Animat ...

  5. 一道在输入上有坑点的LCS

    原题连接 http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=114&p ...

  6. MySQL主主模式+Keepalived高可用

    今天闲来无事,打算搭建一个MySQL的高可用架构,采用的是MySQL的主主结构,再外加Keepalived,对外统一提供虚IP.先来说说背景吧,现在的项目为了高可用性,都是避免单节点的存在的,比如,我 ...

  7. Linux 系统编程 学习:09-线程:线程的创建、回收与取消

    Linux 系统编程 学习:09-线程:线程的创建.回收与取消 背景 我们在此之前完成了 有关进程的学习.从这一讲开始我们学习线程. 完全的开发可以参考:<多线程编程指南> 在Linux ...

  8. 正式班D21

    2020.11.03星期二 正式班D21 目录 11.5 源码包 11.5.1 预先安装编译安装依赖的库 11.5.2 官网下载源码包 11.5.3 解压.编译.编译安装 11.5 源码包 11.5. ...

  9. 微信小程序新闻网站列表页

    在app.json中可以设置所有文件的头部导航颜色 (是window属性的子属性) 在具体页面可以单独设置该页面的导航颜色 (直接写该属性,不需要写window属性) 查看官方文档,可以看到好多全局属 ...

  10. C# 集合类(四)

    C# 集合类自己经常用到: 数组(Array).动态数组(ArrayList).列表(List).哈希表(Hashtable).字典(Dictionary),对于经常使用的这些数据结构,做一个总结,便 ...