深入理解Java虚拟机 字节码执行引擎

来源:互联网 发布:高考心态调整 知乎 编辑:程序博客网 时间:2024/06/13 09:34

运行时栈帧结构

在JVM基本框架中已经提到过栈帧Frame结构。

这里写图片描述


局部变量表

  • 局部变量表以Slot为基本单位,int,float,reference,boolean, byte都占1 Slot;long和double数据被切割成连续2 Slots。
  • 局部变量中Slot可重用,当方法体内定义的变量超出其作用域时,会被重用。

这里写图片描述


操作数栈

  • JVM对栈帧做了优化处理,令下面的栈帧的操作数栈和上面的栈帧局部变量表部分重叠,重叠部分就是调用方法所需的参数部分。

这里写图片描述


动态链接
Reference to runtime constant pool for class of this method.


执行引擎

虚拟机如何执行字节码指令?

Java程序可以被虚拟机进行解释执行,也可以被虚拟机编译成本地代码后执行。主流JVM支持在解释执行的接触上,对部分“热点代码”通过及时编译器(JIT)进行编译执行。


纯解释执行
示例,图片来源:关于Java程序的执行的一次分享

void foo(){    int a = 1;    int b = 2;    int c = (a + b) * 5;}

这里写图片描述


运行期优化执行

对于HotSpot,在程序刚开始执行时使用解释执行的方式,省去编译时间;随着程序的执行,JVM将频繁执行的“热点代码”编译成本地代码,提高执行效率。

这里写图片描述


即时编译触发条件

Hot Spot Code:

  • 被多次调用的方法。HotSpot JVM利用方法调用计数器(Invocation Counter)记录,触发后编译整个方法。
  • 被多次执行的循环体,利用回边计数器(Back Edge Counter)(字节码中指令向后跳即位回边),触发后依然编译整个方法,成为OSR栈上替换。

方法调用计数器和回边计数器都存在于方法的内存模型中,编译器通过两个计数器之和是否大于阈值,来确定该方法是否需要即时编译。


即时编译触优化技术

HotSpot对即时编译JIT做了大量编译优化技术,使被编译的本地代码有较高执行效率。其中Client Compiler关注局部性优化,编译较快;而Server是充分优化过的高级编译器,编译时间较长,编译后代码质量很高。

  • 方法内联:重要的优化手段,能够去除方法调用成本。但是对于虚方法,编译期间无法确定使用哪个版本,需要特殊处理。

这里写图片描述


  • 逃逸分析:如果一个对象不会被外部方法引用,不会被外部线程访问,即不会逃逸到方法或线程之外,可以做如下优化:
    • 对象栈上分配,减小GC压力
    • 消除线程同步

方法调用


方法调用指令

  • invokestatic:调用静态方法
  • invosepcial:调用私有方法、父类方法和实力构造器<init>方法
  • invokevirtual:调用所有虚方法
  • invokeinterface:会在运行时确定实现此接口的对象
  • invokedynamic:现在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。


解析

其中invokestatic和invosepcial指令调用的方法版本,编译器可知,运行期不可变,因此会在类加载时的链接之解析阶段将符号引用直接翻译成确定的直接引用。

这类方法的调用成为解析调用


分派

分派调用过程解释多态的最基本体现。

静态分派:对应方法重载(Overload),编译器根据参数的静态类型而不是实际类型确定重载版本,并把符号引用写了方法调用的invokevirtual指令的参数中。
因此,静态派发在编译阶段已经完成。

public class SimpleClass {    static abstract class Human{}    static class Man extends Human{}    public void sayHello(Human guy) {        System.out.println("human");    }    public void sayHello(Man man) {        System.out.println("man");    }    public static void main(String[] args){        SimpleClass sc = new SimpleClass();        Human man = new Man();        sc.sayHello(man);     //静态类型为Human    }}

输出结果

Human



动态分派:对应方法重写(Override),根据接受者的动态类型决定调用版本,只有在运行期间确定。
动态分派的实现方式:在为类在方法区建立虚方法表vtable,虚方法表中对应各个虚方法的实际入口地址。实现与C++虚函数表类似。

public class SimpleClass {    static abstract class Human{        public abstract void sayHello();    }    static class Man extends Human{        @Override        public void sayHello() {            System.out.println("man");        }    }    static class Woman extends Human{        @Override        public void sayHello() {            System.out.println("woman");        }    }    public static void main(String[] args){        SimpleClass sc = new SimpleClass();        Human man = new Man();        Human woman = new Woman();        man.sayHello();        woman.sayHello();    }}

输出结果

man
woman



单分派和多分派:Java支持静态多分派和动态单分派。

0 0
原创粉丝点击