1. 即时编译
1.1. 分层编译
JVM 的执行状态分为5个层次:
- 0层,解释执行(Interpreter)。
- 1层,使用 C1 即时编译器编译执行(不带profiling)。
- 2层,使佣 C1 即时编译器编译执行(带基本的profiling)。
- 3层,使用 C1 即时编译器编译执行(带完全的profiling)。
- 4层,使用 C2 即时编译器编译执行。
profiling 是指在运行过程中收集一些程序执行状态的数据, 例如方法的调用次数,循环的回边次数等。
即时编译器(JIT)与解释器的区别:
- 解释器是将字节码解释为机器码,下次即使遇到相同的字节码, 仍会执行重复的解释。
- JIT 会将一些热点字节码编译为机器码,并存入Code Cache, 下次遇到相同的代码,直接执行,无需再编译。
- 解释器是将字节码解释为针对所有平台都通用的机器码。
- JIT 会根据平台类型,生成平台特定的机器码。
对于占据大部分的不常用代码,无需耗费时间将其编译成机器码,而是采取解释执行的方式运行。
对于仅占据小部分的热点代码,可以将其编译成机器码,以达到更高的运行速度。执行效率上 Interpreter < C1 < C2。
【代码演示】
public class Test {public static void main(String[] args) {for (int i = 0; i < 200; i++) {long start = System.nanoTime();for (int j = 0; j < 1000; j++) {new Object();}long end = System.nanoTime();System.out.printf("%d\t%d\n", i, (end - start));}}}
【运行结果】
0 47301
1 80481
2 34613
……
134 53937
135 36098
136 652 // 逃逸分析发现 new Object(); 根本没用到,索性直接不创建
137 641
……
197 645
198 672
199 626
1.2. 方法内联
方法内联就是把调的方法的代码复制到调用方法中,减少方法调用的技术。
【方法调用过程】
- 首先会有个执行栈,存储它们的局部变量、方法名、动态连接。
- 当一个方法被调用,一个新的栈帧会被加到栈顶,分配的本地变量和参数会存储在这个栈帧。
- 跳转到目标方法代码执行。
- 方法返回的时候,本地方法和参数被销毁,栈顶被移除。
- 返回原来的地址执行。
当一个方法体不大,但又频繁被调用时,时间和空间开销会相对变得很大,降低了程序的性能。
【代码演示】
public class Test {public static void main(String[] args) {System.out.println(square(9));}private static int square(final int i) {return i * i;}}// 内联优化后public class Test {public static void main(String[] args) {System.out.println(9 * 9);}private static int square(final int i) {return i * i;}}// 再进行常量折叠优化后public class Test {public static void main(String[] args) {System.out.println(81);}private static int square(final int i) {return i * i;}}
【总结】
- 方法体尽量做到更小。
- 尽量使用 final、private、static 修饰符。
2. 反射优化
当反射方法被反复执行时,次数达到一定阈值(15)时,会改变调用方法,提升性能。
public class Test {public static void main(String[] args) throws Exception {Method foo = Test.class.getMethod("foo");for (int i = 0; i < 20; i++) {foo.invoke(null);}}public static void foo() {}}
foo.invoke(null); 在第0~15次调用时,使用 MethodAccessor 接口的 NativeMethodAccessorImpl 子类实现,性能相对较低。超过15次后,会在运行时根据当前方法调用信息,动态生成一个新的方法访问器,然后直接调用该方法。
【invoke 源码分析】
class NativeMethodAccessorImpl extends MethodAccessorImpl {private final Method method;private DelegatingMethodAccessorImpl parent;private int numInvocations;NativeMethodAccessorImpl(Method method) {this.method = method;}public Object invoke(Object obj, Object[] args)throws IllegalArgumentException, InvocationTargetException{if (++numInvocations > ReflectionFactory.inflationThreshold()&& !method.getDeclaringClass().isHidden()&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {// 当调用累加次数>15时,会根据当前方法调用信息,动态生成一个新的方法访问器(动态生成字节码)MethodAccessorImpl acc = (MethodAccessorImpl)new MethodAccessorGenerator().generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers());parent.setDelegate(acc);}// 当调用累加次数<=15时,使用native方法实现return invoke0(method, obj, args);}void setParent(DelegatingMethodAccessorImpl parent) {this.parent = parent;}private static native Object invoke0(Method m, Object obj, Object[] args);}
运行时动态生成的代码:
public class GeneratedMethodAccessor1 extends MethodAccessorImpl{public Object invoke(Object object, Object[] arrobject) throws InvocationTargetException {try {// 直接通过代码调用的方式,实现反射调用Test.foo();return null;} catch (Throwable throwable) {throw new InvocationTargetException(throwable);} catch (ClassCastException | NullPointerException runtimeException) {throw new IllegalArgumentException(Object.super.toString());}}}
