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