程序编译与代码优化

概述

编译器是一段“不确定”的操作过程

编译器类型

  1. 前端编译器:将Java代码编译为class字节码
  • 代表:sun公司的javac(Java语言编写)、Eclipse JDT中的增量编译器ECJ
  1. 后端编译器(JIT编译器):将字节码转变为机器码
  • 代表: HotSpot VM的C1、C2编译器
  1. 静态提前编译器(AOT编译器 Ahead of Time Compiler):将Java代码编译为机器码
    -代表:GNU Compiler for the java、Excelsior JET

1. 早期(编译期)优化

主要说明Sun Javac的大概编译过程

  1. 解析与填充符号表过程
    1.1 解析:
    1.1.1 词法分析:将源代码的字符流转为标记(Token)集合(例如:关键字,变量名,运算符,字面量)
    1.1.2 语法分析:根据Token序列构造抽象语法树,语法树的每一个节点都是一个语法结构(例如:包,类型,修饰符,运算符,接口,返回值,代码注释)
    1.2 符号表填充:符号表由符号地址和符号信息组成的表格(可以看成K-V键值对)
  2. 注解处理
    插入式注解处理器的标准API在编译期间对注解进行处理
  3. 分析与生成字节码
    因为由于由语法分析所生成的抽象语法树不能保证逻辑性,故而语义分析是对正确性的审查
int a =1;
boolean b = false;
int c = a+b;//编译不能通过
  • 3.1 标注检查
    主要检查:变量使用前是否已经被声明、变量与赋值之间的数据类型是否能匹配
  • 3.2 数据及控制流分析
    主要检查:对程序的上下文更进一步的验证,如:局部变量在使用前是否已经赋值、方法的每条路径是否都有返回值、是否所有的受检异常都被正确的处理等问题
//这里的两个方法,编译出的字节码没有一点区别,只是有final修饰的不能被改变
public void test(final int a) {}
public void test(int a) {}
  • 3.3 解语法糖
    泛型擦除(实际上就是将结果强转)、变长参数、自动装箱/拆箱、内部类、枚举类、断言语句、对枚举和字符串的支持、try语句定义和关闭资源等
    条件编译:使用条件为常量的if语句
//1. 
public static void main (String[] args) {
    if (true) {
        System.out.println("1");
    } else {
         System.out.println("2");
      }
}
//在编译之后的结果
public static void main (String[] args) {
    System.out.println("1");   
}
//2. 下面语句将会拒绝编译
public static void main(String[] args) {
        while (false) {
            System.out.println("错误");
        }
    }
  1. 字节码生成
    字节码生成不仅仅将前面步骤生成的信息转换为字节码写到磁盘中,还要进行少量的代码添加和转换工作

2. 晚期(运行期)优化

部分商用虚拟机(Sun HotSpot、IBM J9)中,Java程序最初通过解释器(interpreter)解释执行,当虚拟机发现某个方法或者代码运行特别频繁时,就会把这些代码定义为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。完成这个任务的编译器就是即时编译器(JIT just int time complier)
JRockit没有解释器,因此启动时间较长

  1. 为何HotSpot 虚拟机要使用解释器与编译器并存的架构?
    两种编译器各有优势: 解释器:启动快、执行快; 编译器:执行效率高
    Client模式启动速度较快,Server模式启动较慢,但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多
  2. 为何HotSpot 虚拟机要实现两个不同的即使编译器?
    client compiler:获取更快的编译速度;server compiler:获取更好的编译质量。具体使用哪种,虚拟机会根据自身版本和宿主机的硬件性能自由选择,当然也可以强制运行某种模式,无论编译器采用client compiler 还是server compiler,解释器与编译器搭配使用都称为“混合模式(mixed mode)”,也可以强制虚拟机运行编译模式(-Xcomp)或者解释模式(-Xint)
MacBook-Pro:~ shizhenshuang$ java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)
MacBook-Pro:~ shizhenshuang$ java -Xint -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, interpreted mode)
MacBook-Pro:~ shizhenshuang$ java -Xcomp -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, compiled mode)
  1. 程序什么时候用解释器执行?什么时候用编译器执行?
    当程序刚开始启动的时候,解释器优先执行,省去编译时间,当程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码。那么,什么时候编译器开始执行呢?

编译对象(热点代码)与触发条件

  • 被多次调用的方法
  • 被多次调用的循环体(实际编译的事整个方法)
    方法替换使用的是栈上替换(On Stack Replacement)

判断一段代码是不是热点代码,是否需要触发即时编译,这样的行为成为热点探测。热点探测目前流行的有两种方法

  1. 基于采样的热点探测
    虚拟机周期性检查各个线程的栈顶,如果出现某个(或某些)方法经常出现在栈顶,那就是热点方法
  2. 基于计数器的热点探测
    虚拟机为某个方法或者代码块建立计数器,统计执行次数,如果执行次数超过一定的阈值就认为它是热点代码。(阈值:client模式是1500,server模式是10000,可以通过参数:-XX:CompileThreshold 设置)
    计数器又分为:
    2.1 方法调用计数器
方法调用计数器触发即时编译

2.2 回边计数器

回边计数器触发编译条件
  1. 如何从外部观察即时编译器编译过程和编译结果?
    使用debug,fastdebug版本的虚拟机(JDK6u25之后就不提供下载了),运行时,添加参数-XX:+PrintCompilation
  2. 编译优化项
    代表
  • 1 方法内联:1. 除去方法调用的成本;2. 为其他优化建立良好的基础
class A {
    int age;

    public int getAge() {
        return age;
    }
}
    public static void main(String[] args) {
        A a = new A();
        int y = a.getAge();
    }
//优化后的代码如下(用Java代码表示)
  public static void main(String[] args) {
        A a = new A();
        int y = a.age;
    }
  • 2 消除冗余代码
  • 3 代码复写传播
int y = 2;
z = y;
int sum = y+ z;
//复写传播
y=y;
int sum = y+y;
//消除冗余代码后
int sum = y+y;

典型代表
1. 公共子表达式消除:如果一个表达式E已经计算过了,并且从先前的计算到现在E中的所有变量的值都没有改变,那么E的这次出现就成为了公共子表达式,也就没必要再花时间对他进行计算了(若a+b=c, 则int i = a+b+1 --> int i = c+1)
2. 数组范围检查消除:1. 编译时检查,2. 通过数据流分析
3. 方法内联:就是把目标方法的代码拷贝到调用方,避免发生真实的方法调用
4. 逃逸分析(JDK1.6开始):它并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术。逃逸分析的基本行为就是分析对象动态作用域。当一个对象在方法中被定义,通过参数的形式传递到其他方法中,称为方法逃逸。甚至有可能被外部线程访问到,譬如赋值给类变量或在其他线程中访问实例变量,称为线程逃逸。如果能够确定一个对象不会逃逸到方法或者线程之外,则可以为这个对象做一些高效的优化
4.1 栈上分配:将对象分配在栈上,内存空间随着栈帧出栈而销毁,减少gc回收的压力
4.2 同步消除:如果确定一个对象不会逃逸出线程,就无须对这个对象实施通过的措施(线程同步是一个相对耗时的过程)
4.3 标量替换:标量是指不能再拆解的数据类型,如原始数据类型。如果一个数据还能被分解,它就称为聚合量,如对象。标量替换就是将对象的成员变量替换为原始的数据类型。逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆解,那么程序执行的时候可能就不会真正的创建这个对象,而是创建它的若干个被这个方法访问的成员变量

  1. Java与C++的编译器对比
    即:即时编译器与静态编译器的对比
    1. 即时编译器占用的是用户运行的时间,具有很大的时间压力。而编译时间成本在静态编译器中并不是主要关注点
    1. Java语言是动态类型安全语言。这就意味着由虚拟机来确保程序不会违反语义和非结构化内存,这就使得虚拟机得频繁的动态检查空指针,数组下标范围,类型转换等
    1. Java中虽然没有virtual关键字,但是接受者进行动态选择的频率要远远大于C/C++语言,优化难度要大于静态编译器
    1. Java语言是动态扩展语言,运行时加载新的类可能会改变程序类型的继承关系
    1. Java语言中的对象都是在堆上分配,只有方法中的局部变量才在栈上分配。而C/C++则有多种内存分配,既可以在堆上,也可以在栈上。C/C++主要是用户程序代码回收内存分配,因此效率上要高于Java
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342