作业5:Java编译原理

时间:2021-12-13 02:09:03

零、编译

1、编译器

(1)前端编译器:.java文件转变为.class文件Sun的javacEclipse JDT中的增量编译器(ECJ)

(2)后端编译器:.class文件转变为机器码HotSpot VM的C1编译器HotSpot VM的C2编译器

(3)AOT编译器:.java文件按直接转变为机器码GNU Compiler for Java(GCJ)Excelsior JET

2、编译过程

作业5:Java编译原理

一、前端编译

1、Javac编译过程

  • 解析与填充符号表过程

    • 语法、词法分析
    • 填充符号表
  • 插入式注解处理器的注解处理过程

  • 分析与字节码生成过程

    • 标注检查
    • 数据及控制流分析
    • 解语法糖
    • 字节码生成

    作业5:Java编译原理

initProcessAnnotations(processors);  // 准备过程:初始化插入注解处理器
// These methoad calls must be chained to avoid memory leaks
delegateCompiler =
processAnnotations( //2:执行注解处理
enterTrees(stopIfError(CompileState.PARSE, // 1.2:填充符号表
parseFiles(sourceFileObjects))), // 1.1:词法分析与语法分析
classnames); delegateCompiler.compile2(); //3:分析及字节码生成 // inner compile2
case BY_TODO:
while(!todo.isEmpty())
generate( // 3.4:生成字节码
desuger( // 3.3:解语法糖
flow( //3.2: 数据流分析
attribute(todo.remove())))); // 3.1:标注
break;

2、解析与填充符号表

(1)词法分析
  • 定义:词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写的最小元素,而标记是编译过程的最小元素。如:关键字、变量名、字面量、运算符都能成为标记。
  • 解析类:com.sun.tools.javac.parser.Scanner
(2)语法分析
  • 定义:语法分析是根据Token序列构造抽象语法树的过程,抽象语法树(AST)是一种用来描述程序代码中的一个语法结构,如包、类型、修饰符、运算符、接口、返回值等。
  • 解析类:com.sun.tools.javac.parser.JavacParser

3、填充符号表

(1)定义:符号表(Symbol Table)是一组符号地址与符号信息构成的表格。

(2)符号表中记录的信息在编译的不同阶段都要用到,如:语义分析时,符号表中的内容用于语义检查(名字与原先的说明是否一致)与生成中间代码;在目标代码生成阶段,对地址名进行地址分配就是根据符号表的记录。(3)解析类:com.sun.tools.javac.comp.Enter

4、注解处理器

(1)JDK1.5,java语言提供了注解的支持,但当时只能在运行期发挥作用。

(2)JDK 1.6,提供了插入式注解处理器的标准API在编译期间对注解进行处理。这些注解处理器能在处理注解期间对语法树进行修改,所以需要回到解析以及填充符号表的过程,这称为一个Round。

(3)处理类:com.sun.tools.javac.processing.JavacProcessingEnvironment

5、语义分析:分析对结构正确的源程序进行上下文有关性质的审查,如:类型审查。

(1)标注检查

  • 检查内容:变量使用前是否被声明、变量与赋值之间的数据类型是否能匹配等
  • 常量折叠:常量相加变为一个常量
  • 例子:int a = 1 + 2;  =>  int a = 3;
  • 解析类:com.sun.tools.javac.comp.Attr、com.sun.tools.javac.comp.Check

(2)数据及控制流分析

  • 数据及控制流分析是对程序上下文逻辑更进一步的验证
  • 验证内容:局部变量在使用前是否赋值、方法的每条路径是否有返回值、是否所有的受检异常被正确处理。
  • 例子:final 只在编译期间保证变量的不变性
  • 解析类:com.sun.tools.javac.comp.Flow

(3)解语法糖

  • 语法糖:JVM不支持的语法,但为了让程序员编程简单而添加的高级语法,所以编译过程需要将高级语法还原为简单的基础语法结构。
  • 例子:增强for循环 => 迭代器循环
  • 解析类:com.sun.tools.javac.comp.TransTypes 、com.sun.tools.javac.comp.Lower

6、字节码生成

  • 前面各个步骤的信息(语法树、符号表)转化为字节码写入磁盘

  • 少量的添加和转换工作

    • 如:字符串加法 => StringBuilder的append方法;
    • 如:类构造器和实例构造器的生成(顺序为父类的构造器先执行)
  • 关联类:com.sun.tools.javac.jvm.Gen、com.sun.tools.javac.jvm.ClassWriter

7、常见语法糖的奥秘

(1)泛型与类型擦除

Java的泛型基于类型擦除,在编译期间就把泛型变为原来的裸类型。

List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
System.out.println(list.get(0)); ============================> List list = new ArrayList();
list.add("hello");
list.add("world");
System.out.println((String)list.get(0));

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

List<Integer> list2 = Arrays.asList(1,2,3,4,5,6);
int sum = 0;
for (int i : list2)
sum += i; ============================> List list2 = Arrays.asList(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4), Integer.valueOf(5), Integer.valueOf(6) });
int sum = 0;
for (Iterator localIterator = list2.iterator(); localIterator.hasNext(); ) {
int i = ((Integer)localIterator.next()).intValue();
sum += i;
} // 自动装箱 int -> Integer
1 -> Integer.valueOf(1) // 自动拆箱 Integer -> int
Integer::intValue() // 增强for循环
for(int i : list)
->
for(Iterator localIterator = list.iterator(); localIterator.hasNext(); ){
int i = localIterator.next();
} // 自动装箱以及自动拆箱的陷阱
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d); // true
System.out.println(e == f); // false,==不遇到算术运算符不自动拆箱(即两个Integer比较)
System.out.println(c == (a + b)); // true
System.out.println(c.equals(a + b)); // true
System.out.println(g == (a + b)); // true
System.out.println(g.equals(a + b)); // false 注意:equals方法不处理数据转换,==方法不遇到算术运算符不会自动拆箱。 Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(2);
Integer c = Integer.valueOf(3);
Integer d = Integer.valueOf(3);
Integer e = Integer.valueOf(321);
Integer f = Integer.valueOf(321);
Long g = Long.valueOf(3L);
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c.intValue() == a.intValue() + b.intValue());
System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
System.out.println(g.longValue() == a.intValue() + b.intValue());
System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue()))); // Integer 与 Long比较

(3)条件编译:com.sun.tools.javac.comp.Lower完成

if (true){
System.out.println("true");
}else{
System.out.println("false");
} ===============================> System.out.println("true");

二、后端编译

1、JIT编译器

  • 概述:JIT编译期能在JVM发现热点代码时,将这些热点代码编译成与本地平台相关的机器码,并进行各个层次的优化,从而提高热点代码的执行效率。

  • 热点代码:某个方法或代码块运行频繁。

  • JIT编译器(Just In Time Compiler):即时编译器。

  • 目的:提高热点代码的执行效率。

2、解释器与编译器

作业5:Java编译原理

(1)并存的优势

  • 程序需要迅速启动和执行时,解释器先发挥作用,省去编译时间,且随时间推移,编译器将热点代码编译本地代码,提高执行效率。
  • 当运行环境内存资源限制较大(嵌入式)时,使用解释执行节约内存,反之使用编译执行提升效率。
  • 解释器能作为编译器激进优化的逃生门,如:编译优化出现问题,能退化为解释器状态执行 。

(2)Hotspot虚拟机内置的JIT编译器

  • C1编译器(Client Compiler):更高的编译速度
  • C2编译器(Server Compiler,Opto编译器):更好的编译质量

(3)JVM的运行模式

  • 混合模式(Mixed Mode):使用解释器 + 其中一个JIT编译器(-client / -server 指定使用哪个)
  • 解释模式(Interpreted Mode):只使用解释器(-Xint 强制JVM使用解释模式)
  • 编译模式(Compiled Mode):只使用编译器(-Xcomp JVM优先使用编译模式,解释模式作为备用)

(4)编译层次:

  • 第0层:程序解释执行,解释器不开启性能监控功能,可触发第1层编译
  • 第1层:C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要将加入性能监控逻辑
  • 第2层:C2编译,同1层优化,但启动了一些编译耗时较长的优化,甚至根据性能监控信息进行不可靠的激进优化

3、编译对象与触发条件

(1)编译对象(热点代码)

  • 被多次调用的方法:整个方法为编译对象
  • 被多次执行的循环体(一个方法中):整个方法为编译对象

循环体编译优化发生在方法执行过程中,称为栈上替换(On Stack Replacement,简称OSR编译,机方法栈帧还在栈上,方法就被替换了)

(2)热点代码探测判定

  • 基于采样的热点探测:周期检查各个线程的栈顶,发现某个方法经常出现栈顶,即热点方法。
    • 实现简单,高效
    • 容易获取方法调用关系(堆栈中展开即可)
    • 但很难准备确定一个方法的热度
  • 基于计数器的热点探测(Hotspot JVM采用):为每个方法(代码块)建立计数器,统计方法的执行次数,次数超过阈值就认定为热点方法。
    • 实现复杂,每个方法维护一个计数器
    • 不能直接获取方法调用关系
    • 但统计准确和严谨

(3)基于计数器的热点探测分类

  • 方法调用计数器:统计方法调用次数
    • 阈值修改:-XX:CompileThreshold
    • 设置半衰周期(周期内没有达到阈值将减半):-XX:CounterHalfLifeTime (单位 s)
    • 关闭热度衰减:-XX:-UseCounterDecay
  • 回边计数器:统计一个方法中的循环体代码执行次数,字节码中遇到控制流向后跳转的执行称为”回边“。
    • 阈值修改:-XX:BackEdgeThreshold
    • 间接调整阈值: -XX:OnStackReplacePercentage
      • Client模式下:方法调用计数器阈值 *  OSR比率 / 100
      • Server模式下:方法调用计数器阈值 * (OSR比率 - 解释器监控比率<默认33>) / 100

作业5:Java编译原理

作业5:Java编译原理

4、后台执行编译优化过程(-XX:BackgroudCompilation设置来禁止后台编译)

作业5:Java编译原理

(1)一个平*立的前端将字节码构造成一种高级中间代码(HIR,High Level Intermediate Representation)表示。HIR使用静态单分配的形式代表代码值,使得HIR的构造过程中和之后进行优化动作更容易实现。在此之前会进行基础优化,如:方法内联、常量传播等。

(2)一个平台相关的后端从HIR中产生低级中间代码(LIR)表示,而在此之前进行HIR上的优化,如:空值检查、范围检查消除等

(3)平台相关的后端使用线性扫描算法在LIR上分配寄存器,并在LIR上做窥孔优化,产生机器代码。

5、编译优化技术

(1)优化技术概述

// 优化前
static class B{
int value;
final int get(){
return value;
}
} public void foo(){
y = b.get();
// ...do stuff
z = b.get();
sum = y + z;
} // 内联优化后
public void foo(){
y = b.value;
// ...do stuff
z = b.value;
sum = y + z;
} // 冗余访问消除或公共子表达式消除后
public void foo(){
y = b.value;
// ...do stuff
z = y;
sum = y + z;
} // 复写传播后
public void foo(){
y = b.value;
// ...do stuff
y = y;
sum = y + y;
} // 无用代码消除后
public void foo(){
y = b.value;
// ...do stuff
sum = y + y;
}

(2)公共子表达式消除 :一个表达式已经被计算过且从先前的计算到现在都没发生变化,那么E就成为了公共子表达式。

// 未优化前
int d = (c * b) * 12 + a + (a + b + c); // 公共子表达式消除后
int E = c * b;
int d = E * 12 + a + (E + a); // 代数化简后
int d = 13 * E + 2 * a;

(3)数组边界检查消除

  • foo[3] 数组下标为常量,编译期期间根据数据流分析来确定foo.length的值,并判断下标 3 没有越界,则执行的时候无需判断。
  • 数组访问发生在循环中,循环变量来访问数组,如果编译器通过数据流分析得知循环变量的取值在区间内,则可以把循环中的数组上下界检查消除。

(4)方法内联

  • 优点
    • 去除调用方法的成本(如建立栈帧)
    • 为其他优化建立基础
  • 涉及技术:用于解决多态特性。
    • 类型继承关系分析(CHA,Class Hierarchy Analysis)技术:用于确定目前的加载类中,某个接口是否有多个实现,某个类是否有子类和子类是否抽象等。
    • 内联缓存:在未发生方法调用前,内联缓存为空,第一次调用后,缓存下方法接收者的版本信息,并且每次进行方法调用时都比较接收者版本,如果每次调用方法接收者版本一样,那么内联缓存可以继续用。但发现不一样时,即发生了虚函数的多态特性时,取消内联,查找虚方法表进行方法分派。
// 优化前
public static void foo(Object obj){
if(obj != null){
Sout("do something");
}
} public static void testInline(String[] args){
Object obj = null;
foo(obj);
} // 优化后
public static void testInline(String[] args){
Object obj = null;
if(obj != null){
Sout("do something");
}
}

(5)逃逸分析 :为其他优化提供分析手段

  • 基本行为:分析对象的作用域
    • 方法逃逸:当一个对象在方法里面被定义后,它可能被外部方法所引用。如:作为调用参数传递到其他方法中。
    • 线程逃逸:当一个对象在方法里面被定义后,它可能被外部线程访问到。如:类变量或可被访问到实例变量

(6)根据逃逸分析证明一个对象不会逃逸到方法或线程中,则进行高效的优化

  • 栈上分配:JVM中,对象一般在堆中分配,堆是线程共享的,进行垃圾回收和整理内存都是消耗时间的。所有确定一个对象不会逃逸时,让对象从栈上分配内存可以缓解垃圾回收的压力。
  • 同步消除:如果确定一个变量不会逃逸出线程,则消除掉变量的同步措施。
  • 标量替换:聚合量 => 拆开 => 成员变量恢复为原始变量 => 标量。
    • 标量是一个数据已经无法再分解成更小的数据,JVM中的原始数据类型(int、long等数值类型以及reference类型等)。反之为聚合量,如Java对象。
    • 如果对象不会逃逸,则不创建该对象。方法执行时直接创建若干个相关的变量来替代。并且对象拆分后,对象的成员变量在栈上分配和读写,为进一步优化提供条件。

6、Java与C/C++编译器对比

(1)Java的劣势:

  • JIT即时编译器运行占用用户运行时间
  • Java语言时动态的类型安全语言,JVM频繁进行动态检查,如:实例方法访问时检查空指针、数组元素访问检查上下界、类型转换时检查继承关系
  • Java使用虚方法频率高于C/C++,即多态选择频率大于C/C++。
  • Java是可以动态拓展的语言,运行时加载新的类可能改变程序类型的继承关系,即编译器需要时刻注意类型变化并在运行时撤销或重新进行一些优化。
  • Java对象在堆上分配,垃圾回收比C/C++语言由用户管理开销大。

(2)Java的优势:

  • 开发效率高
  • C/C++编译器属于静态优化,不能在运行期间进行优化。如:
    • 调用频率预测
    • 分支频率预测
    • 裁剪未被选择的

作业5:Java编译原理的更多相关文章

  1. Java编译原理

    http://wenku.baidu.com/view/f9b1734b87c24028915fc3a3.html Java编译原理 1. 关于动态加载机制 学习Java比C++更容易理解OOP的思想 ...

  2. JAVA - JAVA编译运行过程

    Java编译原理 *.java→*.class→机器码 java编译器 (编译) → 虚拟机(解释执行) →  解释器(翻译) → 机器码 1.Java编译过程与c/c++编译过程不同 Java编译程 ...

  3. Java编译过程、c&sol;c&plus;&plus;编译过程区别

    Java编译原理 1.Java编译过程与c/c++编译过程不同 Java编译程序将java源程序编译成jvm可执行代码--java字节码. c/c++编译过程: 当C编译器编译生成一个对象的代码时,该 ...

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

    16 个回答 默认排序​ RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...

  5. 【转】变量的声明和定义&comma;从C到编译原理到C&plus;&plus;&comma;再到Java

    基础学了太久,时间一长有些东西就可能记得不太清楚,俗话说得好,"好记性不如烂笔头",所以把基础中的基础-变量的声明和定义,从C到编译原理到C++,再到Java用烂笔头记录下来 最早 ...

  6. Java的编译原理

    概述 java语言的"编译期"分为前端编译和后端编译两个阶段.前端编译是指把*.java文件转变成*.class文件的过程; 后端编译(JIT, Just In Time Comp ...

  7. Java 实现《编译原理》中间代码生成 -逆波兰式生成与计算 - 程序解析

    Java 实现<编译原理>中间代码生成 -逆波兰式生成与计算 - 程序解析 编译原理学习笔记 (一)逆波兰式是什么? 逆波兰式(Reverse Polish notation,RPN,或逆 ...

  8. Java 实现《编译原理》简单-语法分析功能-LL&lpar;1&rpar;文法 - 程序解析

    Java 实现<编译原理>简单-语法分析功能-LL(1)文法 - 程序解析 编译原理学习,语法分析程序设计 (一)要求及功能 已知 LL(1) 文法为: G'[E]: E→TE' E'→+ ...

  9. Java 实现《编译原理》简单词法分析功能 - 程序解析

    Java 实现<编译原理>简单词法分析功能 - 程序解析 简易词法分析功能 要求及功能 (1)读取一个 txt 程序文件(最后的 # 作为结束标志,不可省去) { int a, b; a ...

随机推荐

  1. 初识App安全性测试

    目前手机App测试还是以发现bug为主,主要测试流程就是服务器接口测试,客户端功能性覆盖,以及自动化配合的性能,适配,压测等,对于App安全性测试貌似没有系统全面统一的标准和流程,其实安全性bug也可 ...

  2. js获取浏览器地址

    <script type="text/javascript"> window.onload = function(){ var txt=""; va ...

  3. Hand 3D Pose Estimation

    https://cvarlab.icg.tugraz.at/projects/hand_detection/

  4. text-rendering 详解

    原文链接:http://www.feeldesignstudio.com/2013/05/text-rendering Text-rendering 属性是一个非标准属性,主要用来告诉渲染引擎(ren ...

  5. CSS传统布局之页面布局实例

    传统的页面布局依赖于盒模型+流动模型(flow)+浮动模型(float)+层模型(layer)来实现页面的布局,具体方法是通过盒模型+display属性+float属性+position属性来加以实现 ...

  6. mysql全备份脚本速查

    mysql全备份脚本 # 快捷备份方式[root@nb scripts]# cat db.backup.sh #!/bin/bashmysqldump -ubackup -pbackuppwd -P3 ...

  7. git 在本地拉取远程分支的代码(并不做提交操作)

    1. git fetch 获取远程的所有分支 2. 在执行 git checkout -b local-branch-name origin/remote-branch  就可以将远程分支remote ...

  8. LogUtil工具

    package com.develop.web.util; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logg ...

  9. Python中sys和os模块的区别

    sys: This module provides access to some variables used or maintained by the interpreter and to func ...

  10. 线程中的定时器Timer类

    Timer 定时器 几分钟之后执行一个任务. 创建了一个定时器相当于开启了一条线程,TimerTask相当于一个线程的任务.内部使用wait/notify机制来实现的. 用法非常的简单  就足以里面的 ...