美文网首页
7、第四部分 程序编译与代码优化-第10章 前端编译与优化

7、第四部分 程序编译与代码优化-第10章 前端编译与优化

作者: 站得高看得远 | 来源:发表于2021-07-18 12:43 被阅读0次

    概述

    • 前端编译器:把.java文件转变成.class文件的过程

      例如:JDK的Javac、Eclipse JDT中的增量式编译器(ECJ)

    • 即时编译器(常称JIT编译器,Just In Time Compiler)运行期把字节码转变成本地机器码的过程。

      例如:HotSpot虚拟机的C1、C2编译器,Graal编译器

    • 提前编译器(常称AOT编译器,Ahead Of Time Compiler)直接把程序编译成与目标机器指令集相关的二进制代码的过程。

      例如:JDK的Jaotc、GNU Compiler for the Java(GCJ)

    Javac编译器

    Javac本身就是一个由Java语言编写的程序

    Javac的源码与调试

    下载openJDK8的代码,JDK_SRC_HOME/langtools/src/share/classes/com/sun/*目录下的源文件全部复制到工程的源码目录中



    从Javac代码的总体结构来看,编译过程大致可以分为1个准备过程和3个处理过程:

    1. 准备阶段:初始化插入式注解处理器
    2. 解析与填充符号表过程,包括:
      • 词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。
      • 填充符号表。产生符号地址和符号信息。
    3. 插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段,本章的实战部分会设计一 个插入式注解处理器来影响Javac的编译行为。
    4. 分析与字节码生成过程,包括:
      • 标注检查。对语法的静态信息进行检查。
      • 数据流及控制流分析。对程序动态运行过程进行检查。
      • 解语法糖。将简化代码编写的语法糖还原为原有的形式。
      • 字节码生成。将前面各个步骤所生成的信息转化成字节码。

    上述3个处理过程里,执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号,从总体来看,三者之间的关系与交互顺 序如图10-4所示。



    Javac编译动作的入口是 com.sun.tools.javac.main.JavaCompiler类,上述3个过程的代码逻辑集中在这个类的compile()和compile2() 方法里


    解析与填充符号表

    1. 词法、语法分析
    2. 填充符号表

    注解处理器

    语义分析与字节码生成

    1. 标注检查
    2. 数据及控制流分析
    3. 解语法糖
    4. 字节码生成

    Java语法糖的味道

    泛型

    泛型的本质是参数化类型(Parameterized Type)或者参数化多态(Parametric Polymorphism)的应用,即可以将操作的数据类型指定为方法签名中的一种特殊参数,这种参数类型能够用在类、接口和方法的创建中,分别构成泛型类、泛型接口和泛型方法。

    1. Java与C#的泛型
      Java选择的泛型实现方式叫作“类型擦除式泛型”(Type Erasure Generics)
    //Java中不支持的泛型用法
    public class TypeErasureGenerics<E> {
        public void doSomething(Object item) {
            if (item instanceof E) { // 不合法,无法对泛型进行实例判断
                ...
            }
            E newItem = new E(); // 不合法,无法使用泛型创建对象
            E[] itemArray = new E[10]; // 不合法,无法使用泛型创建数组
        }
    }
    
    1. 类型擦除
      裸类型应被视为所有该类型泛型化实例的共同父类型(Super Type)
    import java.util.ArrayList;
    
    //裸类型赋值
    public class Test {
        public static void main(String[] args) {
            ArrayList<Integer> ilist = new ArrayList<Integer>();
            ArrayList<String> slist = new ArrayList<String>();
            ArrayList list; // 裸类型
            list = ilist;
            list = slist;
        }
    }
    
    //泛型擦除前的例子
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test {
        public static void main(String[] args) {
            Map<String, String> map = new HashMap<String, String>();
            map.put("hello", "你好");
            map.put("how are you?", "吃了没?");
            System.out.println(map.get("hello"));
            System.out.println(map.get("how are you?"));
    
        }
    }
    
    //泛型擦除后的例子
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test {
        public static void main(String[] args) {
            Map map = new HashMap();
            map.put("hello", "你好");
            map.put("how are you?", "吃了没?");
            //加了类型强转
            System.out.println((String) map.get("hello"));
            System.out.println((String) map.get("how are you?"));
        }
    }
    
    //原始类型的泛型(目前的Java不支持)
    //不支持int、long与Object之间的强制转型
    ArrayList<int> ilist = new ArrayList<int>();
    ArrayList<long> llist = new ArrayList<long>();
    ArrayList list;
    list = ilist;
    list = llist;
    
    import java.util.List;
    //当泛型遇见重载1
    //这段代码是不能被编译的,因为参数List<Integer>和List<String>编译之后都被擦除了,变成了同一种的裸类型List,类型擦除导致这两个方法的特征签名变得一模一样
    public class GenericTypes {
        public static void method(List<String> list) {
            System.out.println("invoke method(List<String> list)");
        }
    
        public static void method(List<Integer> list) {
            System.out.println("invoke method(List<Integer> list)");
        }
    }
    

    擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们在编码时能通过反射手段取得参数化类型的根本依据。

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

    import java.util.*;
    
    //自动装箱、拆箱与遍历循环
    public class Test {
        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(1, 2, 3, 4);
            int sum = 0;
            for (int i : list) {
                sum += i;
            }
            System.out.println(sum);
        }
    }
    
    //自动装箱、拆箱与遍历循环编译之后
    /*代码中包含了泛型、自动装箱、自动拆箱、遍历循环与变长参数5种语法糖,泛型,自动装箱、拆箱在编译之后被转化成了对应的包装和还原方法,如本例中的Integer.valueOf()与Integer.intValue()方法,而遍历循环则是把代码还原成了迭代器的实现,这也是为何遍历循环需要被遍历的类实现Iterable接口的原因。最后再看看变长参数,它在调用的时候变成了一个数组类型的参数*/
    public class Test {
        public static void main(String[] args) {
            List 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);
        }
    }
    

    条件编译

    //Java语言的条件编译
    public class Test {
        public static void main(String[] args) {
            if (true) {
                System.out.println("block 1");
            } else {
                System.out.println("block 2");
            }
        }
    }
    
    //Class文件的反编译结果:
    public class Test {
        public static void main(String[] args) {
                System.out.println("block 1");
        }
    }
    

    源自书籍:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)-周志明

    相关文章

      网友评论

          本文标题:7、第四部分 程序编译与代码优化-第10章 前端编译与优化

          本文链接:https://www.haomeiwen.com/subject/exncmltx.html