方法内联、逃逸分析

1,267 阅读3分钟

1、什么是方法内联?

方法内联:指的是在即时编译过程中遇到方法调用时,直接编译目标方法的方法体,并替换原方法调用。

  • 方法内联属于即时编译期的优化技术;
  • 即时编译的过程是字节码被解析成IR图,优化IR图,再由优化过的IR图生成机器码的过程;
  • 解析指的是即时编译器对字节码做的动作,是字节码到IR图的转换;

2、什么是逃逸分析?

逃逸分析:Escape Analysis简单来讲就是,Java Hotspot 虚拟机可以分析新创建对象的使用范围,并决定是否在 Java 堆上分配内存的一项技术。 逃逸分析的 JVM 参数如下:jdk6之后默认开启逃逸分析 开启逃逸分析:-XX:+DoEscapeAnalysis 关闭逃逸分析:-XX:-DoEscapeAnalysis 显示分析结果:-XX:+PrintEscapeAnalysis

  • 锁消除
  • 标量替换
  • 栈上分配

3、反射为什么慢?

  • fan射的实现如下代码
// v0 版本
import java.lang.reflect.Method;

public class Test {
  public static void target(int i) {
    new Exception("#" + i).printStackTrace();
  }

  public static void main(String[] args) throws Exception {
    Class<?> klass = Class.forName("Test");
    Method method = klass.getMethod("target", int.class);
    method.invoke(null, 0);
  }
}

##Method的invoke方法
public final class Method extends Executable {
  ...
  public Object invoke(Object obj, Object... args) throws ... {
    ... // 权限检查
    MethodAccessor ma = methodAccessor;
    if (ma == null) {
      ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
  }
}

反射的实现过程,分别调用了Class.forName、Class.getMethod、Method.invoke方法

  • Class.forName:会调用本地方法,可想而知个操作非常费时。
  • Class.getMethod:则会遍历该类的公有方法。如果没有匹配到,它还将遍历父类的公有方法,也挺耗时。
  • Method.invoke:这个方法的实现,它实际上委派给MethodAccessor来处理。MethodAccessor是一个接口,它有两个已有的具体实现。 一个通过本地方法来实现反射调用,另一个则使用了委派模式。为了方便记忆,我便用“本地实现”和“委派实现”来指代这两者。动态实现和本地实现相比,其运行效率要快上20倍。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上3到4倍。考虑到许多反射调用仅会执行一次,Java 虚拟机设置了一个阈值 15(可以通过 -Dsun.reflect.inflationThreshold= 来调整),当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现,这个过程我们称之为 Inflation(方法内联)。
  • Method.invoke方法的反射调用会带来不少性能开销,原因主要有三个:变长参数方法导致的 Object 数组,基本类型的自动装箱、拆箱,还有最重要的方法内联。
JVM方法调用的指令
  • invokeinterface:用以调用接口方法,在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。(Invoke interface method)
  • invokevirtual:指令用于调用对象的实例方法,根据对象的实际类型进行分派(Invoke instance method; dispatch based on class)
  • invokestatic:用以调用类方法(Invoke a class (static) method )
  • invokespecial:指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。(Invoke instance method; special handling for superclass, private, and instance initialization method invocations )
  • invokedynamic:JDK1.7新加入的一个虚拟机指令,相比于之前的四条指令,他们的分派逻辑都是固化在JVM内部,而invokedynamic则用于处理新的方法分派:它允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,从而达到动态语言的支持。(Invoke dynamic method)