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。

【代码演示】

  1. public class Test {
  2. public static void main(String[] args) {
  3. for (int i = 0; i < 200; i++) {
  4. long start = System.nanoTime();
  5. for (int j = 0; j < 1000; j++) {
  6. new Object();
  7. }
  8. long end = System.nanoTime();
  9. System.out.printf("%d\t%d\n", i, (end - start));
  10. }
  11. }
  12. }

【运行结果】

0 47301

1 80481

2 34613

……

134 53937

135 36098

136 652 // 逃逸分析发现 new Object(); 根本没用到,索性直接不创建

137 641

……

197 645

198 672

199 626

1.2. 方法内联

方法内联就是把调的方法的代码复制到调用方法中,减少方法调用的技术。

【方法调用过程】

  • 首先会有个执行栈,存储它们的局部变量、方法名、动态连接。
  • 当一个方法被调用,一个新的栈帧会被加到栈顶,分配的本地变量和参数会存储在这个栈帧。
  • 跳转到目标方法代码执行。
  • 方法返回的时候,本地方法和参数被销毁,栈顶被移除。
  • 返回原来的地址执行。

当一个方法体不大,但又频繁被调用时,时间和空间开销会相对变得很大,降低了程序的性能。

【代码演示】

  1. public class Test {
  2. public static void main(String[] args) {
  3. System.out.println(square(9));
  4. }
  5. private static int square(final int i) {
  6. return i * i;
  7. }
  8. }
  9. // 内联优化后
  10. public class Test {
  11. public static void main(String[] args) {
  12. System.out.println(9 * 9);
  13. }
  14. private static int square(final int i) {
  15. return i * i;
  16. }
  17. }
  18. // 再进行常量折叠优化后
  19. public class Test {
  20. public static void main(String[] args) {
  21. System.out.println(81);
  22. }
  23. private static int square(final int i) {
  24. return i * i;
  25. }
  26. }

【总结】

  • 方法体尽量做到更小。
  • 尽量使用 final、private、static 修饰符。

2. 反射优化

当反射方法被反复执行时,次数达到一定阈值(15)时,会改变调用方法,提升性能。

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. Method foo = Test.class.getMethod("foo");
  4. for (int i = 0; i < 20; i++) {
  5. foo.invoke(null);
  6. }
  7. }
  8. public static void foo() {
  9. }
  10. }

foo.invoke(null); 在第0~15次调用时,使用 MethodAccessor 接口的 NativeMethodAccessorImpl 子类实现,性能相对较低。超过15次后,会在运行时根据当前方法调用信息,动态生成一个新的方法访问器,然后直接调用该方法。

【invoke 源码分析】

  1. class NativeMethodAccessorImpl extends MethodAccessorImpl {
  2. private final Method method;
  3. private DelegatingMethodAccessorImpl parent;
  4. private int numInvocations;
  5. NativeMethodAccessorImpl(Method method) {
  6. this.method = method;
  7. }
  8. public Object invoke(Object obj, Object[] args)
  9. throws IllegalArgumentException, InvocationTargetException
  10. {
  11. if (++numInvocations > ReflectionFactory.inflationThreshold()
  12. && !method.getDeclaringClass().isHidden()
  13. && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
  14. // 当调用累加次数>15时,会根据当前方法调用信息,动态生成一个新的方法访问器(动态生成字节码)
  15. MethodAccessorImpl acc = (MethodAccessorImpl)
  16. new MethodAccessorGenerator().
  17. generateMethod(method.getDeclaringClass(),
  18. method.getName(),
  19. method.getParameterTypes(),
  20. method.getReturnType(),
  21. method.getExceptionTypes(),
  22. method.getModifiers());
  23. parent.setDelegate(acc);
  24. }
  25. // 当调用累加次数<=15时,使用native方法实现
  26. return invoke0(method, obj, args);
  27. }
  28. void setParent(DelegatingMethodAccessorImpl parent) {
  29. this.parent = parent;
  30. }
  31. private static native Object invoke0(Method m, Object obj, Object[] args);
  32. }

运行时动态生成的代码:

  1. public class GeneratedMethodAccessor1 extends MethodAccessorImpl{
  2. public Object invoke(Object object, Object[] arrobject) throws InvocationTargetException {
  3. try {
  4. // 直接通过代码调用的方式,实现反射调用
  5. Test.foo();
  6. return null;
  7. } catch (Throwable throwable) {
  8. throw new InvocationTargetException(throwable);
  9. } catch (ClassCastException | NullPointerException runtimeException) {
  10. throw new IllegalArgumentException(Object.super.toString());
  11. }
  12. }
  13. }