这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战
前端编译
前端编译是指java源码编译为字节码过程。
Java泛型的实现
Java通过泛型擦除实现泛型。因为基本类型与对象之前不能强制转换(int与object之间不能强制转换),所以只能使用包装类型List,而包装类又存在自动拆箱、装箱的开销,所以Java泛型的性能比较低,如果需要专门优化,可以自己编写指定类型的容器,如IntFloatMap
,避免自动装箱、拆箱的性能影响。
后端编译
热点代码编译优化
JVM的运行模式有三种:
- 解释执行
- 编译执行
- 混合模式(前两种都有)
解释执行速度慢,但是启动块。编译执行,编译慢,但是运行块。JVM为了两者兼顾,通常工作在混合模式下。 可以通过java参数指定运行模式。
JVM运行时,其内部通常有一个解释器,2个编译器(客户端编译器、服务端编译器,也称作c1、c2编译器)。 当JVM刚开始运行,使用解释器执行虚拟机字节码,当运行一段时间,JVM会监控热点代码,然后通过编译器将热点代码编译为机器码直接执行。c1、c2的区别可以想象成c1轻量(CAS)、c2重量(Synchorize)。
方法内联
除了热点代码编译优化之外,还有方法内联优化。比如:
class A{
public void hello() {
B b1;
B b2;
int a = b1.get();
int b = b2.get();
System.out.println(a+b);
}
}
class B {
public int val;
public int get() {
return val;
}
}
当A类中的hello方法执行时候,会创建三个栈帧,hello方法和2个B类的get方法。如果已经检测到变量b1,b2没有父类或者子类,那么就可以直接将B类的get方法的代码直接拷贝到方法的调用处,从而减少栈帧的产生。 hello方法可能被优化成:
class A{
public void hello() {
B b1;
B b2;
int a = b1.val;
int b = b2.val;
System.out.println(a+b);
}
}
逃逸分析
对于一个对象,定义其三种逃逸状态:
- 不逃逸,对象的作用域只在执行的方法内。
- 方法逃逸,对象的作用域通过方法返回值传递到另外一个方法中
- 线程逃逸,对象的作用域在多个线程中
对于以上三种情况可以做以下的优化:
-
对于不逃逸的对象,可以使用栈上分配对象,分配在栈帧中,而栈帧空间会随着方法的调用而回收,所以当方法执行完后直接就可以回收对象的内存。
-
对于非线程逃逸的对象,对其变量的写操作,不需要考虑并发的特性。