
JVM总结-06_编译器优化
方法内联
编译器最重要的优化手段。
-
去除方法调用时的冗余代码,把方法之间内部关联起来,以减少方法调用的成本,例如查找方法版本,建立栈帧
-
为其他优化提供基础
java默认的实例方法是虚方法(方法在运行时支持多态,而invokespecial指定的私有方法,实例构造器,父类方法和使用invokestatic修饰的静态方法是在编译器确定的,是非虚方法)
-
非虚方法直接内联
-
虚方法,CHA类型继承关系分析,分析它的继承关系
-
若只有一个确定的版本(不多态),直接内联
-
若有多个版本,建立缓存减少开销
-
若多次是同一个版本,建立单态内联缓存
-
不一致,退化成超多态内联缓存
-

逃逸分析
为其他优化提供的分析技术
分析对象的作用域:当一个对象在方法里被定义后,可能会被外部方法引用,这就是方法逃逸,该对象可能被其他线程引用,这就是线程逃逸
从不逃逸 ->方法内自产自销
方法逃逸 ->方法内对象被其他方法使用
线程逃逸 -> 方法内对象被其他线程方法使用
逃逸分析根据逃逸不同程度做优化,如
-
栈上分配:一般情况,创建的对象在堆内存中,若能确定该对象为方法逃逸级别,可在栈上分配该对象空间,这样就能随着方法调用结束,直接销毁对应的栈帧就行,不用再放堆里等着回收了。
-
标量替换:若一个对象被分解到无法用另一个对象替换就是标量,若还可分解就是聚合量,若一个对象只在方法内使用,且可以被拆散,可以尝试替换成标量,避免创建对象
-
同步消除:线程同步耗时,若能确定变量不会逃逸出线程,不会被其他线程访问到,就不需要对它进行读写竞争同步,对这个变量的同步措施可以安全的消除掉
公共子表达式擦除
若一个子表达式在前面完完全全出现过,且先前的计算到现在的所有的变量值没有任何变化,就直接用之前的值,不用重新计算
数组边界检查擦除
在使用数组的时候每次都会对数组的边界进行检查,数组越界会报错,如foo[4];
编译器分析出这个数组不会超出某个范围时,就可不必在每次都进行数组边界检查,就擦除掉了。
判断循环语句
判断对应各种字节码指令,ifne iflt等
循环其实就是判断指令配合goto语句
多态
在类加载的连接期间,确定好类的引用地址(准备阶段,字面引用替换为直接引用),虚方法,执行invokevirtual
异常
底层有一个异常表
记录各个分支跳转对应的字节码指令
finally的块会在所有分支复制一份
finally里写return会吞掉异常
synchronized
也是用到了异常表,确保出现异常的时候能顺利解锁
通过monitorenter和monitorexit指令来对代码块加锁解锁

语法糖-编译期处理
-
默认无参构造,类里不写无参默认生成一个无参,调用super(),父类的init方法
-
自动装拆箱,装箱 Integer.valueOf 拆箱 intvalue
-
泛型擦除,泛型在编译期擦除为Object
-
可变参数,String ...args == String [] args
-
foreach,for: ==for i
-
switch string,比较字符串实际上比较的是串的hashCode
-
switch enum,定义一个jvm可见的合成类ordinal,根据标识的序号判断分支
-
枚举,继承Enum<T>,编译构造方法等
-
try with resource,实现了AutoCloseAble接口的类,不用写finally,在资源使用完后会自动调用对应的close方法
-
重写桥接,子类重写父类方法,返回值可以为父类返回值的子类,此时编译器生成jvm可见的桥接方法,返回值是父类返回值
-
匿名内部类,new Runnable(){ pv run(){}} -》 public class abc$1 impl Runnable{ pv run(){}}