JVM编译

125 阅读2分钟

这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

前端编译

前端编译是指java源码编译为字节码过程。

Java泛型的实现

Java通过泛型擦除实现泛型。因为基本类型与对象之前不能强制转换(int与object之间不能强制转换),所以只能使用包装类型List,而包装类又存在自动拆箱、装箱的开销,所以Java泛型的性能比较低,如果需要专门优化,可以自己编写指定类型的容器,如IntFloatMap,避免自动装箱、拆箱的性能影响。

后端编译

热点代码编译优化

JVM的运行模式有三种:

  1. 解释执行
  2. 编译执行
  3. 混合模式(前两种都有)

解释执行速度慢,但是启动块。编译执行,编译慢,但是运行块。JVM为了两者兼顾,通常工作在混合模式下。 可以通过java参数指定运行模式。 image.png

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);
}
}

逃逸分析

对于一个对象,定义其三种逃逸状态:

  1. 不逃逸,对象的作用域只在执行的方法内。
  2. 方法逃逸,对象的作用域通过方法返回值传递到另外一个方法中
  3. 线程逃逸,对象的作用域在多个线程中

对于以上三种情况可以做以下的优化:

  • 对于不逃逸的对象,可以使用栈上分配对象,分配在栈帧中,而栈帧空间会随着方法的调用而回收,所以当方法执行完后直接就可以回收对象的内存。

  • 对于非线程逃逸的对象,对其变量的写操作,不需要考虑并发的特性。