JVM虚拟机(二):字节码执行引擎

运行时栈帧结构

    栈帧时用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接、和方法返回地址等信息。

局部变量表

  局部变量表的容量以变量槽为最小单位。每个变量槽应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress(可忽略,现在已经很少见了)。reference类型表示对一个对象实例的引用,即1 根据引用直接或间接的查到对象在java堆中的数据存放的起始地址、索引或对象所属数据类型在方法区中的存储的类型信息。上述类型均占用一个变量槽。long和double占用两个连续的变量槽。

示例1

实例方法(没有被static修饰的方法)局部变量表第0位是this。

public void soltTest() {
    byte i = 15;
}
局部变量表
public void soltTest() {
    long i = 15;
}
局部变量表

  为了尽可能节省栈帧耗用的内存空间,局部变量表中的变量槽是可以重复使用的,方法体中定义的变量作用域没有全部覆盖整个方法,此变量占用的变量槽是可以被重复利用的。

注意:示例需设置虚拟机参数“-verbose:gc”

示例2

public static void main(String[] args) {
    byte[] bytes = new byte[64 * 1024 * 1024];
    System.gc();
}

控制台输出:

[GC (System.gc())  72123K->66690K(251392K), 0.0177919 secs]
[Full GC (System.gc())  66690K->66523K(251392K), 0.0042184 secs]

示例3

public static void main(String[] args) {
    {
        byte[] bytes = new byte[64 * 1024 * 1024];
    }
    System.gc();
}

控制台输出:

[GC (System.gc())  72123K->66674K(251392K), 0.0007715 secs]
[Full GC (System.gc())  66674K->66523K(251392K), 0.0041207 secs]

示例4

public static void main(String[] args) {
    {
        byte[] bytes = new byte[64 * 1024 * 1024];
    }
    int a = 0;
    System.gc();
}

控制台输出:

[GC (System.gc())  72123K->66690K(251392K), 0.0009232 secs]
[Full GC (System.gc())  66690K->987K(251392K), 0.0042235 secs]

结论:变量槽在没有复用时,不GC

操作数栈

操作数栈是后进先出栈。个人感觉操作数栈是局部变量表与方法区中间的数据中转站。

方法调用

  方法调用不等同于方法中的代码被执行,方法调用阶段唯一的任务就是确定调用哪个方法,暂时还未涉及方法内部的具体运行过程 。

解析

调用方法在程序代码写好、编译器进行编译那一刻就确定下来了,这类方法的调用被称为解析。在Java中符合“编译期可知,运行期不可变”要求的方法主要有静态方法和私有方法两大类。

调用不同类型的方法,字节码指令集里面设计了不同的指令。分别是:

  • invokestatic:用于调用静态方法。
  • invokespecial:用于调用实例构造器 ()方法,私有方法和父类中的方法。
  • invokevirtual:用于调用所有的虚方法。
  • invokeinterface:用于调用接口方法,在运行时再确定一个实现该接口的对象。
  • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。

静态方法、私有方法、实例构造器、父类方法及final修饰的方法会在类加载的时候就可以把符号引用解析为该方法的直接引用。这些方法统称为”非虚方法“。

猜你喜欢

转载自www.cnblogs.com/chinda/p/12604664.html