5.1 前端优化(编译期优化) —— 语法糖
- 语法糖的概念
- 前端优化 / 语法糖
- 泛型
- 最常用的语法糖
- 拆箱装箱
- 循环遍历 for( int a : list)
- 变长参数 public static void main( String[] args )
- if语句优化
- 默认构造器
- .....
5.2 后端优化(.class文件 转 二进制机器码 / 运行期)
- 解释器和即时编译器
- JIT优化技术
- 方法内联
- 逃逸分析
- 公共子表达式消除
5.1.1 语法糖的概念
所谓语法糖,就是java编译器把*.java编译成*.class过程中,自动生成和转换的代码
5.1.2 泛型
jdk5之后加入:java在编译后会进行 泛型擦除 的动作,即在字节码文件中,泛型信息丢失,都当作Object来处理
- 缺点1:泛型擦除后无数的拆箱装箱导致泛型慢
- 缺点2:运行期无法得到泛型类型信息
但是,泛型擦除只擦除Code方法表内的信息,在LocalVariableTypeTable中,这些泛型信息仍然会被保留
因此,想要获得泛型信息,可以通过反射的方式获得getGenericParameterTypes()
是否相等:true
List<Integer> integerList = new ArrayList<Integer>();
List<Short> shortList = new ArrayList<Short>();
integerList.getClass().getName() == shortList.getClass().getName();
能通过编译吗? 不能
List<? extends Number> numberList;
numberList.add(2);
泛型详解:
5.1.3 最常用语法糖
- 自动拆箱,装修
- java的基本类型和包装类型可以自动转换,在jdk5之后加入
- 例如:Integet.valueOf() 和 Integer.intValue() 会把 int 和 Integer 相互转换
- 循环遍历
- 例如:for( int a : list)
- 把代码还原成迭代器的实现,这也是为何遍历循环需要被遍历的类实现Iterable接口的原因
- 变长参数
- 例如 public static void main( String[] args )
- 可变参数String...args在编译期间其实是一个String[] args的数组,长度会自动根据参数个数计算(如果不传参,则会传递空数组而不是null)
5.1.4 条件编译
- 编译器会自动优化if语句
- 例如: if ( true ){ ..... } 后面的 else / else if都不会执行
- 例如:while ( false ){ ..... } 中的内容不会执行
5.1.5 默认构造器
- 如果没有写构造器,会自动生成无参构造器,并在构造器内调用父类无参构造器
//自己写的版本
public class Test{
}
-------------------------------------------------------------------
//编译器生成版本
public class Test{
//前端编译器自动加上无参构造器
public Test{
super(); //调用父类无参构造
}
}
5.1.6 静态创建数组和动态创建数组
5.1.7 switch-string
- 会把switch(string)转换成switch(hashcode),相等后再equals比较防止hash冲突;用hashcode比较效率比字符串比较高
5.1.8 switch-枚举类
会把枚举类变成int数组{1,2,3....},用int作比较
5.1.9 枚举类
5.1.10 子类重写父类方法时,返回值不一致
5.2 后端优化——即使编译器与提前编译器
5.2.1 解释器和编译器
- 解释器:java程序最初都是解释器执行,启动快,省去编译时间,立即运行
- 即时编译器(JIT):把越来越多的代码(热点代码)编译成本地代码,获得更高的执行效率
- JAVA中内置了两个即使编译器,分别被称 为“客户端编译器”(Client Compiler)和“服务端编译器”(Server Compiler),或者简称为C1编译器和 C2编译器。(JDK10新增了第三个Graal目标取代C2)
- 热点代码:被多次调用的方法;被多次执行的循环体。JIT都是把整个方法体(不是循环体)即时编译
- 热点探测:用于探测哪些循环体或者方法应该属于热点代码(两种方式:采样或者计数器)
5.2.2 分层编译
分层编译根据编译器编译、优化的规模与耗时,划分出不同的编译层次,其中包括:
5.3 编译器优化技术
Ref:基本功 | Java即时编译器原理解析及实践 - 美团技术团队
5.3.1 方法内联
- 最重要的优化手段
- 如果发现热点代码并且长度不太长,会进行内联:把方法内的代码拷贝,黏贴到调用者的位置
- 非虚方法可以直接进行内联
- 虚方法如果只查询到一个版本,则内联(称为守护内联);如果查询到多个版本,内联缓存
5.3.2 逃逸分析
- 最先进的优化手段
- 分析一个对象的作用域,如果在该方法内创建的对象被外部方法引用,则称为方法逃逸;如果被其他线程引用,则称为线程逃逸
- 如果一个对象不存在方法逃逸或者逃逸程度比较低,则可以优化:
- 栈上分配:支持方法逃逸,不支持线程逃逸
- 标量替换:不去创建对象,而改为创建它的若干个被这个方法使用的成员变量来代替。不支持方法逃逸和线程逃逸
- 同步消除:如果发现不存在线程逃逸,则可以擦除同步措施
5.3.3 公共子表达式消除
int d = (c * b) * 12 + a + (a + b * c);
- 会变成下图,因为检测到c*b = b*c
int d = b * c * 13 + 2 * a;