全球最热门网站,wordpress精美博客主题,网络推广龙岗比较好的,滇中引水建设管理局网站【0】README
0.1#xff09;本文部分文字描述转自 “深入理解jvm”#xff0c;旨在学习 早期#xff08;编译期#xff09;优化 的基础知识#xff1b;
0.2#xff09;本文部分文字描述转自#xff1a; http://www.cnblogs.com/zhouyuqin/p/5223180.html 【1】概述 …【0】README
0.1本文部分文字描述转自 “深入理解jvm”旨在学习 早期编译期优化 的基础知识
0.2本文部分文字描述转自 http://www.cnblogs.com/zhouyuqin/p/5223180.html 【1】概述
1java中的编译期是一段不确定的操作过程process可能是process1指一个前端编译器把 *.java 文件转变为 *.class 文件的过程process2指虚拟机的后端运行期编译器JIT编译器just in time compiler把字节码转变为机器码的过程process3指使用静态提前编译器AOT编译器Ahead Of Time Compiler直接把*.java 文件编译成本地机器码的过程 2下面是编译过程中有代表性的编译器2.1前端编译器Sun 的javacEclipse JDT 中的增量式编译器ECJ2.2JIT编译器HotSpot VM 的C1 C2 编译器2.3AOT编译器 GNU Compiler for the JavaGCJExcelsior JET Attention本文提到的编译器是前端编译器【2】 Javac 编译器【2.1】 Javac 的源码和调试1从 Sun Javac 的代码来看编译过程大致可以分为3个过程分别是干货——编译过程大致分为3个过程step1解析和填充符号表过程step2插入式注解处理器的注解处理过程step3分析与字节码生成过程 2以上3个步骤之间的关系与交互顺序如下图所示2.1Javac 编译动作的入口 是 com.sun.tools.javac.main.JavaCompiler 类上述3个过程的代码逻辑集中在这个类的 compile() 和 compile2 方法中主体代码如下图所示整个编译最关键的处理就由以下8个方法来完成【2.2】解析与填充符号表1解析符号表的步骤词法、语法分析填充符号表1.1词法、语法分析干货——引入词法、语法分析1.1.1词法分析是将源代码的字符流转变为 标记集合单个字符是程序编写过程的最小元素而标记则是编译过程的最小元素关键字、变量名、字面量、运算符都可以成为标记如“int a b2” 包含了6个标记分别是 int, a, , b, , 2 1.1.2语法分析是根据Token 序列构造抽象语法树的过程抽象语法树abstract syntax treeAST是一种用来描述程序代码语法结构的树形表示方式语法树的每一个节点都代表着程序代码中的一个语法结构如包类型修饰符运算符接口返回值等 1.2填充符号表1.2.1符号表定义符号表是一组符号地址和符号信息构成的表格读者可以把它想象成哈希表中键值对的形式1.2.2完成抽象语法树之后下一步就是填充符号表的过程即enterTrees()方法。符号表是由一组符号地址和符号信息构成的表格类似于哈希表中K-V值对的形式。符号表中所登记的信息在编译的不同阶段都要用到。当对符号名进行地址分配时符号表是地址分配的依据。填充过程由com.sun.tools.javac.comp.Enter类实现。 【2.3】注解处理器 1JDK1.5之后Java提供了对注解的支持这些注解与普通的Java代码一样在运行期间发挥作用。 有了编译器注解处理的标准API后我们的代码才有可能干涉编译器的行为由于语法树中的任意元素甚至包括代码注释都可以在插件之中访问到所以使用插入式注解处理器在功能上有很大的发挥空间。 【2.4】语义分析与字节码生成0语法分析之后编译器获得了程序代码的抽象语法树表示语法树能表示一个结构正确的源代码抽象。而语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查如进行类型审查。在Javac编译过程中语法分析过程分为标注检查以及数据及控制流分析两个步骤分别对应着attribute()和flow()方法完成。 1标注检查 标注检查步骤检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。此外这个过程中还有一个重要的步骤称为常量折叠。 标注检查步骤在Javac源码中的实现类是com.xun.tools.javac.comp.Attr和com.sun.tools.javac.comp.Check类。 2数据及控制流分析 数据及控制流分析是对程序上下文逻辑更进一步的验证它可以查出诸如程序员局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。编译期的数据及控制流分析与类加载时的数据及数据流分析的目的基本上是一致的但校验范围有所区别有一些校验项只有在编译期或者运行期才能进行。如将局部变量声明为final对运行期是没有影响的变量的不变性仅仅由编译器在编译期间保障在Javac的源码中数据及控制流分析的入口是flow()方法具体操作由com.sun.tools.javac.comp.Flow类来完成。干货——将局部变量声明为final对运行期是没有影响的变量的不变性仅仅由编译器在编译期间保障 看个荔枝如下3解语法糖 干货——语法糖就是一种编写代码的语法 3.1语法糖是指在计算机语言中添加某种语法这种语法对语言的功能并没有影响但是更方便程序员使用。 3.2Java是一种“低糖语言”常用的语法糖主要是之前提到的泛型、变长参数、自动装箱/拆箱等。虚拟机运行时不支持这些语法它们在编译期还原回简单的基础语法结构这个过程称为解语法糖。解语法糖的过程是由desuger()方法触发的。干货——引入解语法糖的概念 4字节码生成 4.1字节码生成是Javac编译过程的最后一个阶段由com.sun.tools.javac.jvm.Gen类来完成字节码生成阶段不仅仅是把前面各个步骤所生成的信息语法树、符号表转化为字节码写入磁盘中编译器还进行了少量代码添加和转换工作。 4.2完成对语法树的遍历与调整之后就会把填充了所有所需信息的符号表交给com.sun.tools.javac.jvm.ClassWriter类由这个类的wrtieClass()方法输出字节码生成最终的Class文件。 【3】 java语法糖【3.1】泛型和类型擦除1泛型是JDK1.5新增的特性它的本质是参数化类型的应用也就是说所操作的数据类型被指定为一个参数这种参数类型可以用于类、接口和方法的创建中分别称为泛型类、泛型接口和泛型方法。 2与C#的泛型不一样的是Java的泛型只存在于程序源码中在编译后的字节码文件中就已经替换成原来的原生类型也称为裸类型并且在相应的地方插入了强制转型代码。因此对于运行期的Java语言来说ArrayListint与ArrayListString就是同一个类所以泛型技术实际上是Java语言的一颗语法糖Java语言中的泛型实现方法称为类型擦除基于这种方法实现的泛型称为伪泛型。 干货——对于运行期的Java语言来说ArrayListint与ArrayListString就是同一个类 2.1看个荔枝 public class GenericEraseTest {public static void main1(String[] args) { // 泛型擦除前的荔枝MapString, String map new HashMap();map.put(hello, 你好);map.put(how are you?, 吃了没?);System.out.println(map.get(hello));System.out.println(map.get(how are you?));}public static void main(String[] args) { // 泛型擦除后的荔枝Map map new HashMap();map.put(hello, 你好);map.put(how are you?, 吃了没?);System.out.println(map.get(hello));System.out.println(map.get(how are you?));}
} 3故当ListInteger和ListString作为参数时擦除使得两者的特征签名变得一模一样有时可能导致拥有该两个方法参数的方法无法重载。 Attention值得注意的是当出现上述的情况的时候如果返回值不一样的话该两个方法是可以存在于一个Class文件中的总结一下两个方法如果有相同的名称和特征签名但返回值不同那它们也是合法地可以共存于一个Class文件中。 4)擦除法所谓的擦除仅仅是对方的Code属性中的字节码进行擦除实际上元数据中还是保留了泛型信息这也是我们能通过反射手段取得参数化类型的根本依据。干货——擦除法的执行本质 【3.2】自动装箱、拆箱与遍历循环 1自动装箱、自动拆箱与遍历循环这些语法糖无论实现上还是思想上都不能和上文介绍的泛型相比两者的难度和深度都有很大差距但是它们是java 中使用得最多的语法糖 1.1看个荔枝 public class SyntacticSugar {// 自动装箱拆箱与遍历循环public static void main1(String[] args) {ListInteger list Arrays.asList(1, 2, 3, 4);// 如果在jdk1.7中还有另外一颗语法糖 // 能让上面这句代码进一步简写成 ListInteger list [1, 2, 3, 4];int sum 0;for (int i : list) {sum i;}System.out.println(sum);}// 自动装箱拆箱与遍历循环编译之后public static void main(String[] args) {List list Arrays.asList(new Integer[] {Integer.valueOf(1),Integer.valueOf(2),Integer.valueOf(3),Integer.valueOf(4),Integer.valueOf(5)});int sum 0;}
} 1.2再看个荔枝自动装箱的陷阱 public class AutoPackage
{public static void main(String[] args){Integer a 1;Integer b 2;Integer c 3;Integer d 3;Integer e 312;Integer f 312;Long g 3L;System.out.println(c d); // trueSystem.out.println(e f); // falseSystem.out.println(c (ab)); // trueSystem.out.println(c.equals(ab)); // trueSystem.out.println(g (ab)); // trueSystem.out.println(g.equals(ab)); // false}
} 对以上代码的分析Analysis鉴于包装类的 运算在不遇到算术运算的情况下不会自动拆箱以及它们equals方法不处理数据转型的关系作者建议在实际编码中应该尽量避免这样使用自动装箱和拆箱 【3.3】条件编译 1java语言也可以进行条件编译方法就是使用条件为常量的if语句 // 通过if语句实现条件编译
public class ContitionalCompile {public static void main(String[] args) {if(true) {System.out.println(true);} else {System.out.println(false);}}
} 2Java语言中条件编译的实现也是Java语言的一颗语法糖根据布尔常量值的真假编译器将会把分支中不成立的代码块消除掉这是在解语法糖阶段实现的。 3Java语言中还有不少的其他语言糖如内部类、枚举类、断言语句、对枚举和字符串的switch支持、try语句中定义和关闭资源等等。