Java虚拟机-虚拟机字节码执行引擎

1.运行时栈帧结构
       栈帧也叫过程活动记录,是编译器用来进行方法调用和方法执行的一种数据结构,它是虚拟机运行时数据区域中的虚拟机栈的栈元素。栈帧中包括局部变量表,操作数栈,动态链接和方法返回地址以及额外的一些附加信息,在编译过程中,局部变量表的大小已经确定,操作数栈深度也已经确定,因此栈帧在运行的过程中需要分配多大的内存是固定的,不受运行时影响。对于没有逃逸的对象也会在栈上分配内存,对象的大小其实在运行时也是确定的,因此即使出现了栈上内存分配,也不会导致栈帧改变大小。一个线程中,可能调用链会很长,很多方法都同时处于执行状态。对于执行引擎来讲,活动线程中,只有栈顶的栈帧是最有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的字节码指令仅对当前栈帧进行操作。

                                               Java虚拟机-虚拟机字节码执行引擎_第1张图片

2.局部变量表
       方法中的参数和局部变量都存在局部变量表中,局部变量如果在声明时未初始化则无法使用,局部变量表中以32位slot为单位进行存储,slot可以复用但对垃圾收集会有些影响。slot复用,当一个变量的pc寄存器的值大于slot的作用域的时候,slot是可以复用的。下面通过代码解释一下slot复用对垃圾回收的影响。

运行下面的代码:

public class TestDemo {
    public static void main(String[] args) {
        byte[] buffer = new byte[60 * 1024 * 1024];
        System.gc();
    }
}

结果如下图,我们发现即使我们显示调用了垃圾回收,实际上并没有执行垃圾回收。

Java虚拟机-虚拟机字节码执行引擎_第2张图片

再运行下面的代码:

public class TestDemo {
    public static void main(String[] args) {
        {
            byte[] buffer = new byte[60 * 1024 * 1024];
        }
        System.gc();
    }
}

结果如下图,我们发现即使buffer出了作用域也没有被回收,这是因为对slot没有读写操作,虚拟机感受不到slot要被复用。

再运行下面代码:

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

结果如下图,进行了垃圾回收,这是因为有新定义的变量,虚拟机会扫描有没有可以复用的slot,如果发现有大于slot作用域的变量就进行垃圾回收。

再看下面的代码:

public class TestDemo {
    public static void main(String[] args) {
        byte[] buffer = new byte[60 * 1024 * 1024];
        buffer = null;
        System.gc();
    }
}

结果如下图,进行了垃圾回收,所以建议当一个变量不再使用时,将其置为null,帮助进行垃圾回收。

Java虚拟机-虚拟机字节码执行引擎_第3张图片

3.操作数栈
       Java虚拟机的解释执行引擎被称为“基于栈的执行引擎”,其中所致的栈就是指-操作数栈。操作数栈也常被称为操作栈。和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作-压栈和出栈来访问的。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。
虚拟机在操作数栈中存储数据的方式和局部变量区中的是一样的:如int、long、floag、double、reference的存储。对于byte、short以及char类型的值在压入到操作数栈之前,也会被转换为int。
       虚拟机把操作数栈作为它的工作区,大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。比如,iadd指令就要从操作数栈中弹出两个证书,执行加法运算,其结果又压回操作数栈中,下面是一个例子,演示了虚拟机如何把两个int类型的局部变量相加,再把结果保存到第三个局部变量的:
      iload_0     // push the int int local variable 0 onto the stack
      iload_1    // push the int local variable 1 onto the stack
      iadd        // pop two ints,add them,push result
      istore_2 // pop int,store into local variable 2

       在这个字节码序列里,前两个指令iload_0和iload_1将存储在局部变量中索引为0和1的整数压入操作数栈中,其中iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令,istore_2则从操作数栈中弹出结果,并把它存储到局部变量区索引为2的位置。下图表示了这个过程中局部变量表和操作数栈的状态变化:

      Java虚拟机-虚拟机字节码执行引擎_第4张图片

4.动态链接
       静态链接是指在类加载或第一次引用的时候就会转化为直接引用即地址引用。在每一次运行期间都会转化为地址引用,栈帧中都包含一个指向运行时常量池中该栈帧所属方法的引用。有了这个引用,就可以支持方法调用的动态链接。

5.方法返回地址和附加信息
       方法调用时通过一个指向方法的指针指向方法的地址,方法返回时将回归到调用处,那个方法是返回地址。正常返回通过pc寄存器的值,异常返回通过异常表。虚拟机规范中允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中。这部分信息完全取决于虚拟机的实现。

你可能感兴趣的:(Java虚拟机)