虚拟机字节码执行引擎-----方法调用

方法调用阶段唯一的任务就是确定被调用方法的版本(调用的是哪一个方法),暂时还不涉及方法内部的具体运行过程。Class文件的编译过程中 不包含传统编译过程中的“连接”,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址。这给java带来更强的动态扩展功能的同时,也使用java方法的调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。

1.解析

在类加载的解析阶段,会将一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可变得(调用目标在程序代码写好、编译器进行编译时就必须确定下来)这类方法的调用称为解析。

在java语言中符合“编译器可知,运行期不可变”的方法只有类方法和私有方法,因此他们都适用在类加载阶段进行解析。

与之对应的是,java虚拟机里面提供了五条方法调用字节码指令。

   invokestatic 调用静态方法
   invokespecial 调用实例构造器、私有方法和父类方法
   invokevirtual 调用所有的虚方法(可以被覆写的方法都可以称作虚方法,因此虚方法并不需要做特殊的声明,也可以理解为除了用static、final、private修饰之外的所有方法都是虚方法。)注意:虽然final是用此指令调用,但并不是虚方法
 invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象
 invokedynamic 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在java虚拟机内部的,而invokedynamic的分派逻辑是由用户所设定的引导方法决定的

只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,静态方法、实例构造器、私有方法和父类方法这四类在类加载的时候就会把符号引用解析为直接引用。这些方法可以被称为非虚方法。

public  class StaticResolution{

    public static void sayHello(){
        System.out.println("hello world");
   }

 public static void main(String[] args){
        StaticResolution.sayHello();

 }

}

查看这段程序的字节码

发现的确是通过invokestatic命令调用的sayhello();

解析调用是静态的过程,在编译期间就完全确定下来,在类加载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。而分派调用则可能是静态的也可能是动态的,根据分派的宗量数可分为单分派和多分派。

2.分派

可以通过分派调用过程揭示多态性特征的一些最基本的体现,如“重载”和“重写”在java虚拟机中时如何实现的

①静态分派

public class A{

   static abstract class Human{}
   static class Man extends Human{}
   static class Woman extends Human{}

   public void sayHello(Human guy){

       System.out.println("hello guy");
   }
    public void sayHello(Human guy){

       System.out.println("hello guy");
   }
   public void sayHello(Human guy){

       System.out.println("hello guy");
   }
   public static void main(String[] args){
       Human man=new Man();
       Human women=new Woman();
      A a=new A();
      a.sayHello(man);
      a.sayHello(women);
   }

}

输出结果是

hello guy
hello guy

关于重载方法的使用,在方法接受者已经确定是对象“sr”的情况下,使用哪个重载版本,完全取决于传入参数的数量和数据类型。代码中可以地定义了两个编译期类型相同但运行期类型不同的变量,但编译器在重载时是通过参数的编译期类型而不是运行期类型作为判定依据的,并且编译期数据在编译期可知的,因此,在编译阶段,javac编译期会根据传入参数的编译期类型决定使用哪个重载版本。

所有依赖编译期类型来定位方法执行版本的分派动态称为静态分派。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。另外,编译期虽然能确定出方法的重载版本,但是在很多情况下这个重载版本并不是“唯一的”,往往是只能确定一个“更加合适的”版本,我们知道编写代码最重要的就是不能让计算机糊涂,而这种模糊的概念很少见,这是因为字面量是不需要定义的,所以字面量没有显示的编译期类型,它的编译期类型只能通过语言上的规则去理解和推断。

public class Overload{

   public static void sayHello(Object arg){
            System.out.println("hello Object");
   }
    public static void sayHello(int arg){
            System.out.println("hello int ");
   }
    public static void sayHello(long arg){
            System.out.println("hello long");
   }
    public static void sayHello(Character arg){
            System.out.println("hello  Character");
   }
    public static void sayHello(char arg){
            System.out.println("hello char");
   }
    public static void sayHello(char... arg){
            System.out.println("hello  char...");
   }
    public static void sayHello(Serializeable arg){
            System.out.println("hello Serializeable ");
   }
  public static void main(String[] args){

     sayHello(‘a‘);
}
}

输出为

hello char

很好理解,因为‘a‘是char类型的数据,系统自然会选择参数类型为char的重载方法,如果把char类型的重载方法去掉。

会输出

hello int

这时发生了一次自动类型转换,‘a’除了可以代表一个字符串,还可以代表数字97,因此参数类型为int的重载也是合适的,继续去掉int类型的方法

输出

hello long

发生了两次自动类型转换,char->int->long,实际上还可以继续进行char->int->long->float->double,但是代码中没有float和double的重载方法,去掉long的方法

会输出

hello Character

这是发生了一次自动装箱。去掉Character类型。

输出

hello Serializable

这是因为Character实现了Serializable,自动装箱以后找不到装箱类,只能向父类或接口转型,这是如果Character还实现了一个接口,并且这个接口的重载类型也在上面代码中,编译器会糊涂,拒绝编译。这时程序必须显示地指定字面量的编译器类型,再去掉这个方法

hello Object

这时是char装箱后转型为父类了,依次往上搜索。如果把这个也去掉

输出就变成

hello char...

只剩下了这一个sayhello(char... org),说明变长参数的重载优先级是最低的。

这个例子演示了编译期间选择静态分派目标的过程,这个过程也是java语言实现方法重载的本质。

注意:解析和分派之间并不是互斥的,而是是在不同层次上去筛选、确定目标方法的过程。例如:类方法在类加载的时候进行解析,但是类方法也会有重载版本,选择重载版本的过程也是通过静态分派完成的。

②动态分派

动态分派与重写有着密切的关联。

public class A{

   static abstract class Human{
    protected abstract void sayHello();
   }
   static class Man extends Human{
    protected  void sayHello(){

       System.out.println("hello man");
   }
   }
   static class Woman extends Human{
        protected  void sayHello(){

       System.out.println("hello woman");
   }
   }

   public static void main(String[] args){
       Human man=new Man();
       Human women=new Woman();
       man.sayHello();
       women.sayHello();
       man=new Woman();
      man.sayHello();
   }

}
时间: 2024-11-05 16:00:48

虚拟机字节码执行引擎-----方法调用的相关文章

虚拟机字节码执行引擎

在前面的几篇文章里,从Java虚拟机内存结构开始,经历了虚拟机垃圾收集机制.Class类文件结构到后来的虚拟机类加载机制,一步步的进入到了Java虚拟机即Java底层的世界.在有了前面的基础之后,接下来就应该进入Java虚拟机最重要的部分了--虚拟机字节码执行引擎,毕竟,这是Java程序得以在不同机器上运行的核心部分. Java是通过实现Java虚拟机来达到平台无关的."虚拟机"的概念是相对于"物理机"来说的,两种机器都有执行代码的能力,不过物理机是直接面向处理器.

【011】【JVM——虚拟机字节码执行引擎】

 JVM--虚拟机字节码执行引擎 Java 虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,这个概念模型成为各种版本虚机执行引擎的统一外观(Facade).在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择,也可能两者兼备,甚至还可能会包含几个不同级别的编译器执行引擎. 运行时栈帧结构 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈

基于栈的虚拟机字节码执行引擎

一.虚拟机字节码执行引擎概述 虚拟机字节码执行引擎主要就是研究字节码指令具体怎样被执行.对于物理机器,指令的执行是直接建立在OS和硬件的基础上 对于字节码指令的执行就是直接建立在JVM上,然后通过JVM完成具体的字节码指令到机器指令的过程.一般来说虚拟机的执行的 字节码指令是基于栈的不是采用寄存器,主要考虑的原因跨平台. 虚拟机的执行引擎是有JVM规范定义的,可以自己定义指令集以及执行引擎来执行字节码指令.不同的JVM执行引擎的实现可能不同 总体来说一个线程对应的是一个虚拟机栈:线程代码中调用的

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

Java虚拟机--虚拟机字节码执行引擎 所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的栈元素.每一个方法从调用开始到执行完成的过程,都对应一个栈帧在虚拟机栈中的入栈出栈过程. 由于虚拟机栈是线程私有的,所以每一个线程都有一个自己的虚拟机栈,而每个虚拟机栈都是由许多栈帧组成.每一个栈帧都包括 局部变量表 操作数栈 动态连接 方法返回地址 额外附加信息 处于

深入理解JVM虚拟机5:虚拟机字节码执行引擎

虚拟机字节码执行引擎 微信公众号[Java技术江湖]一位阿里 Java 工程师的技术小站.作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux.网络.多线程,偶尔讲点Docker.ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”Java“即可领取 Java基础.进阶.项目和架构师等免费学习资料,更有数据库.分布式.微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南

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

概述 Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade).不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行. 运行时帧栈结构 栈帧(Stack Frame)支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量.操作数栈.动态连接和方法返回地址等信息.方法调用开始到执行完成,对应这一个帧栈在虚拟机栈里面入栈和出栈的过程. 一个线程中

008 虚拟机字节码执行引擎

执行引擎是Java虚拟机最核心的组成部分之一. 物理机的执行引擎建立在处理器.硬件.指令集和操作系统之上的,虚拟机的执行引擎需要自己实现,因此可以自己制定指令集与执行引擎的结构体系,并且支持那些不被硬件直接支持的指令集格式. 1.运行时栈帧结构 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区域的虚拟机栈的栈元素. 栈帧存储了方法的局部变量表.操作数栈.动态连接和方法返回地址. 对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,成为当前栈帧(Current

【JVM】虚拟机字节码执行引擎

概念模型上,典型的帧栈结构如下(栈是线程私有的,也就是每个线程都会有自己的栈). 典型的帧栈结构 局部变量表 存放方法参数和方法内部定义的局部变量.在编译阶段,就在Class文件的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量.(仅仅是变量,不包括具体的对象).</br> 局部变量表内部以变量槽(Variable Slot)为最小单位.对于byte.char.float.int.short.boolean.reference.returnAddress等

Jvm(59),虚拟机字节码执行引擎----运行时栈帧结构

后面讲的所有的东西就是对前面所总览的虚拟机栈的进一步理解. 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)[1]的栈元素.栈帧存储了方法的局部变量表.操作数栈.动态连接和方法返回地址等信息.每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程. 每一个栈帧都包括了局部变量表.操作数栈.动态连接.方法返回地址和一些额外的附加信息.在编译程序代码的时候,