第五部分-虚拟机字节码执行引擎

本文详细介绍了JVM中虚拟机字节码的执行引擎,重点讨论了运行时栈帧的结构,包括局部变量表、操作数栈、动态链接和方法返回地址。此外,还阐述了方法调用的过程,如解析和分派,以及静态分派和动态分派的实现。文章最后提到了JVM对动态语言的支持以及与反射的区别。
摘要由CSDN通过智能技术生成

嘚不嘚:每次看下一部分之前都会串联一下前面的知识,总会发现有卡住的地方,有很多内容都是当时看的时候忽略的或者没有想到的,停下来想想可能有更多的收货。随着以后的深入学习,希望能多了解一些OMM和JVM调优方面的知识,实践中距离这些比较远,希望以后可以多多接触与了解。本章之后,打算更新一个JVM串联的知识点,讲述从类的编译到JVM运行时方法的执行过程。下一步计划学习Effective java 。如发现问题,或者建议希望各位大神不吝赐教。

概述
  • 介绍:前面几章主要介绍了JVM内存布局、字节码文件和类的加载过程,这些是在codeing之后跟JVM字节码执行先相联比较少的一部分。这一章节主要介绍JVM中执行字节码栈帧的各个区域的变化和方法的调用过程。
运行时栈帧的结构
  1. 概述:栈帧是用于虚拟机进行方法调用和方法执行的数据结构,每个栈帧都包括一个指向运行时常量池中的符号引用,用于确定调用相关的方法。当线程进入JVM中,JVM就会为该线程分配虚拟机栈内存资源、PC资源等,而栈帧就是在JVM运行时虚拟机栈的栈元素。当进行方法执行时,伴随着栈帧的压栈,当方法执行完成时,伴随着栈帧的出栈,同时栈帧的内存将会被回收。方法调用和方法执行是不相同的两个概念,方法调用是在编译期、方法执行是在JVM运行期的。栈帧中主要包括局部变量表、操作数栈、动态链接、方法返回地址信息等。下面分别介绍分别介绍下各个部分:
  2. 局部变量表:

    1. 概述:主要存储方法参数和方法内的局部变量信息。执行方法的过程中,被调用方法的参数存储在局部变量表中。而调用方法的参数类型在class文件加载到方法区之后就确定了,由constant_methodref_info表结构中的name_ant_type_index指向的表结构决定的。
    2. 局部变量表的容量:
      1. 单个slot:局部变量表的容量已变量槽(slot)为单位,每个slot单位能够存储占用32以内的内存的变量,例如:boolean、byte、char、short、int、float、reference和returnAddress。前6种类型数据可以按照基本类型去理解,而reference类型表示一个对象的引用,JVM没有指明他的长度和结构,但是至少要通过引用做到两点:一是从该引用直接或者间接查找对象在堆中的数据存储的起始地址,二是确定对象在方法区的类型信息。
      2. 多个slot:long和double数据类型占用两个slot内存空间,对于他们的赋值过程是需要两条指令的。对于局部变量表来说,不会出现安全性问题,因为该部分内存是线程私有的,赋值命令是一个一个执行的,所以不会出现问题。
    3. 参数调用过程:JVM使用局部变量表完成参数值到参数列表的传递过程的,定义接口方法void get(int a,String name),该方法在局部变量表中存在3个slot单位的变量,第0个是默认传递方法所属的对象实例的引用,也就是隐式的传递了this关键字。第二个第三个才是方法中定义的方法的参数值。

    4. 局部变量赋值类变量进:当位于32bit JVM下,long和double数据类型是需要两个slot内存空间的,如果在64bit JVM下,long和double数据类型只需要一个slot内存空间,其他数据类型都占用一个slot内存空间。在32bit JVM下对Long类型的类的实例变量进行赋值时,在多线程情况下,非原子操作可能会造成并发问题,参考博客地址

public class P1 {

    private long b = 0;

    public void set1() {
        b = 0;
    }

    public void set2() {
        b = -1;
    }

    public void check() {
        System.out.println(b);

        if (0 != b && -1 != b) {
            System.err.println("Error");
        }
    }
}

对类的实例变量赋值过程中,需要执行两条指令(两个slot单位指令:mov1,mov2),此时线程1可能执行执行mov1指令,将0的低32为赋值给b,线程2可能执行mov2指令,将1的低32位赋值给b,此时线程3执行check方法,if (0 != b && -1 != b) 而该段代码的值为false,所以程序会输出Error,因为该代码是非原子操作,执行指令mov1时,执行代码 0 != b 执行指令mov2时执行代码-1 != b,所以在输入B的结果时,既不是0也不是-1。如果在64位虚拟机上就不会该情况,b的值要么是0要么是-1。

  1. 操作数栈:
    1. 概述:方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容。也就是说在方法的执行过程中,真正指令的操作对象存储在操作数栈中。例如:void add(){int a = 1;int b = 2 int c = a+b;} 在局部变量表中存在四个slot内存空间,当执行iadd(加法指令)时,a和b的值要存储到操作数栈的最顶部,然后取出操作数栈中的值,然后求和,将结果存入操作数栈中,最后将操作数栈中的结果值赋值到局部变量表。
    2. 方法执行过程中的赋值操作:局部变量表的值赋值到操作数栈、操作数栈中的值赋值给局部变量、常量加载到操作数栈。
  2. 动态连接:在类加载阶段将部分的能够在编译期间确定的符号引用转化为直接引用,部分部分引用需要在运行时才能转化为直接引用,后半句就是动态链接的过程,运行时动态决定调用对象和调用方法。在下面会有详细介绍。
  3. 方法返回地址:方法执行完毕后会存在正常返回和异常返回。第一种情况按照字节码指令返回,也就是返回到上层调用者。第二种异常退出。方法的执行完成意味着JVM虚拟机中的栈帧出栈,紧接着执行虚拟机栈中栈顶元素的栈帧。
方法调用
  1. 概述:方法调用阶段唯一确定的就是调用那个方法,在class文件中只是存储调用方法的引用,而不是方法运行时,真时的入口地址。
  2. 解析:在类加载过程中。一部分符号引用被转化为直接引用,在编译器编译完成后,部分调用方法就转化为直接引用,加载阶段将符号引用转化为直接引用的过程被称为解析。

    1. 解析的方法:静态方法、私有方法、父类方法、实例构造器、final方法(非虚方法)在类加载时就会把符号引用转化为直接引用,除了final方法其余的都是虚方法。解析过程是一个静态的过程,而分派可能是一个静态的过程,也可能是一个动态的过程。
  3. 分派:分派是过程是JAVA多态的一种体现,分派的过程讲述了重载和重写在JVM的实现。

    1. 静态分派:静态分派发生在编译阶段,重载方法的选择在类加载的解析阶段,根据符号引用类型来确定方法调用者所调用的方法,Human h = new Man() 类型Human为静态类型,new Man() 为动态类型,因此在重载方法选择是,根据参数的静态类型选择所调用的方法。重载方法的确认是通过参数的数量和参数的类型决定的,而参数类型的选择是通过静态类型而不是实例类型决定的。

      1. 方法重载时出现了一个比较特别的情况,该类型的重载过程是选择最合适的方法而不是选择唯一方法的过程。类型转化为char->int->long->float->doublevoid choice(long l) choice(c) 调用方法choice的参数可以为c->char/int/long
    2. 动态分派:在JVM运行时,动态的决定调用那个类的方法。

      public class Demo09 {
      
      public static void main(String[] args) {
          Demo09 d = new Demo09();
          /**
           *  重载
           */
          Human h = new Man();
          Woman w = new Woman();
          Man m = new Man();
          d.sayHello(h);
          d.sayHello(new Man());
          d.sayHello(w);
          /**
           * 重写
           */
          h.dynamic(new Man());
          h.dynamic(new Woman());
      }
      public void sayHello(Human man) {
          System.out.println("Human say hello");
      }
      public void sayHello(Man man) {
          System.out.println("Man say hello");
      }
      public void sayHello(Woman man) {
          System.out.println("Woman say hello");
      }
      static abstract class Human {
          abstract void dynamic(Human man);
      }
      static class Man extends Human {
          @Override
          void dynamic(Human man) {
              System.out.println("man human");
          }
      }
      static class Woman extends Human {
          @Override
          void dynamic(Human man) {
              System.out.println("woman human");
          }
      }
      }

    根据运行时类的实际类型去选择要调用那个类型的那个方法,参数是new Man() 的,选择调用类Man和该类的dynamic方法。
    JVM运行h.dynamic()方法时,JVM执行invokevirtual指令,该指令的运行时解析过程:
    (1)找到操作数栈的栈顶元素所指向的对象的实际类型,记为C。
    (2)在类型C中查找简单名称和描述符相符的方法,同时对方法权限进行校验,如果通过则返回该方法的直接引用。
    (3)否则在类的继承类或者实现类中查找符合的方法。

    1. 单分派和多分派:静态分派的过程中,首先要通过静态类型确认调用的类型,然后通过方法参数确认调用的方法。动态分派的过程中,只需要通过确认调用方法的实际类型即可。
    2. 虚拟机动态分派的实现:动态分派的过程是非常频繁的,而且方法的选择需要要运行时的类型中查找合适的方法。查找合适方法的优化实现通过在类中建立虚方法表,接口创建接口方法表。虚方法表中存储方法的直接引用,每个方法都会指向它的所属类型,虚方法表是在类的初始化阶段就已经初始化完成。
  4. 动态语言的支持:

    1. 区别:静态语言在编译完成后,对于方法的调用过程和方法的执行过程来说,方法的调用者和方法执行过程中的参数就已经是直接或者间接确定的,对于方法的重载,在类加载完成后确定了方法参数的静态类型,对于方法重写时,在执行调用方法的字节码指令时动态的决定了调用者的实际类型。对于既没有重写或者重载的方法,要么在编译时,要么在执行时就能确定调用的类型和调用的方法。动态语言:JVM在执行invokedynamic指令的每一处都会出现动态调用点,只有在动态调用点时才能确定调用类和调用类的方法。对于这个constant_invokedynamic_info我暂时还没有什么了解,以后遇到了把这段缺失的补上的。
    2. 动态调用语言和反射的区别:动态代理动态的生成了class文件,然后通过反射完成对代理类方法的调用。
      1. 反射是在模拟java代码层次的方法调用,动态语言是模拟字节码层次的方法调用。
      2. 反射可以对整个类型信息就行操作,而动态语言只是针对某个确定的方法。
      3. 动态语言在JVM内存可以进行优化,而反射不能够进行字节码指令优化。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值