vlambda博客
学习文章列表

JVM_3_程序编译与代码优化

1. 编译器

1.1 分类及说明

  • 前端编译器: 把*.java文件转变为*.class文件
    • Javac、ECJ
  • 后端运行期编译器(JIT编译器): 把字节码(*.class文件)转变为机器码
    • HotSpot的C1、C2
  • 静态提前编译器(AOT编译器): 把*.java文件编译成本地机器代码
    • GCJ


2. Java语法糖(编译器优化)

语法糖

2.1 默认构造器

public class Candy{
}

编译成class后的代码:

public class Candy{
    //这个无参构造是编译器加上的
    public Candy(){
        super(); //调用父类Object的无参构造方法
    }
}

2.2 自动拆装箱

public class Candy{
    public static void main(String[] args){
        Integer x = 1;
        int y = x;
    }
}

编译成class后的代码:

public class Candy{
    public static void main(String[] args){
        Integer x = Integer.valueOf(1); //编译器自动装箱
        int y = x.intValue(); //编译器自动拆箱
    }
}

2.3 泛型集合取值

public class Candy{
    public static void main(String[] args){
        List<Integer> list = enw ArrayList<>();
        list.add(10); //实际调用的是 list.add(Object o)
        Integer x = list.get(0); //实际调用的是 Object obj = List.get(int index);
    }
}
JVM_3_程序编译与代码优化
2.3.1 泛型擦除

擦除的是字节码上的泛型信息。

2.4 可变参数

JVM_3_程序编译与代码优化
2.4.1 可变参数
JVM_3_程序编译与代码优化
2.4.2 可变参数
JVM_3_程序编译与代码优化
2.4.3 可变参数

2.5 foreach循环

JVM_3_程序编译与代码优化
2.5.1 foreach
JVM_3_程序编译与代码优化
2.5.2 数组的foreach
JVM_3_程序编译与代码优化
2.5.3 集合的foreach
JVM_3_程序编译与代码优化
2.5.4 foreach

2.6 switch 字符串

JVM_3_程序编译与代码优化
2.6.1 switch-字符串
JVM_3_程序编译与代码优化
2.6.2 switch-字符串
JVM_3_程序编译与代码优化
2.6.3 switch-字符串
JVM_3_程序编译与代码优化
2.6.4 switch-字符串
JVM_3_程序编译与代码优化
2.6.5 switch-字符串
JVM_3_程序编译与代码优化
2.6.6 switch-字符串

2.7 switch 枚举

JVM_3_程序编译与代码优化
2.7.1 switch-枚举
JVM_3_程序编译与代码优化
2.7.2 switch-枚举

2.8 枚举类

JVM_3_程序编译与代码优化
2.8.1 枚举类
JVM_3_程序编译与代码优化
2.8.2 枚举类

2.9 try-with-resources

JVM_3_程序编译与代码优化
2.9.1 try-with-resource
JVM_3_程序编译与代码优化
2.9.2 try-with-resource
JVM_3_程序编译与代码优化
2.9.3 try-with-resource


3. 运行期优化

3.1 JIT编译器

  • 字节码转换为 机器码

3.1.1 解释器和编译器

HotSpot同时包含解释器和编译器。解释器和编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。因此,在整个虚拟机的执行架构中,解释器与编译器经常配合工作。

即时编译器(JIT)和解释器的区别:

  • 解释器即使下次遇到相同的字节码,仍会执行重复的解释;
  • JIT编译器将编译的机器码存入code cache,下次遇到相同,直接执行;
  • 解释器将字节码解释为针对所有平台都通用的机器码;
  • JIT会根据平台类型,生成平台特定的机器码;
JVM_3_程序编译与代码优化
解释器和编译器
JVM_3_程序编译与代码优化
总结

3.1.2 HotSpot虚拟机

内置两个即时编译器,分别称为Client Compiler和Server Compiler或者简称为C1编译器和C2编译器。

  • JVM分为Server模式和Client模式,对应的编译器也有C1(Client Complier)和C2(Server Complier)两个编译器。C1编译器注重编译的速度,C2编译器注重编译的质量。
  • 不论在Server模式还是Client模式,解释器只有一个。早期的JVM中解释器只能和C1或C2中的一个混合运作(当然也可以通过设置虚拟机参数,强制只有编译器运作或只有解释器运作)。
  • 在JDK1.7中,分层编译的加入,让两个编译器和一个解释器可以共同参与工作。

3.1.3 分层编译

  • 第0层:编译器不工作,只有解释器解释执行程序,可触发第二层编译。
  • 第1层:解释器和C1编译器共同工作。把热点字节码编译为机器码,并进行简单可靠的优化,如有必要会开启性能监测。可触发        第2层编译。
  • 第2层(或第2层以上):解释器和C2编译器共同工作。把热点字节码编译为机器码,开启性能监测,并通过监测结果进行激进         优化。

在开启分层编译后,由于两个编译器的加入,许多代码会被多次编译。既通过C1得到了更高的编译速度,也通过C2得到了更高的编译质量,是JVM代码执行系统的一大进步。

3.2 即时编译

3.2.1 分层编译

  • TieredCompilation
JVM_3_程序编译与代码优化
即时编译案例
即时编译优化的分析

3.2.2 方法内联

  • Inlining
方法内联

3.2.3 逃逸分析

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

开启逃逸分析,并查看分析结果:

-XX:+DoEscapeAnalysis +PrintEscapeAnalysis

关闭逃逸分析:

-XX:-DoEscapeAnalysis
- END -