【Java进阶笔记】运行期优化(即时编译、反射优化)

658 阅读3分钟

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