类加载 静态分派 动态分派,java动态分配

  类加载 静态分派 动态分派,java动态分配

  00-1010程序运行时,进行方法调用是最常见、最频繁的操作。

  方法调用不等于方法执行:

  方法调用阶段的唯一任务是确定被调用方法的版本,即调用哪个方法不涉及方法内部的具体运行过程。类文件的编译过程不包括传统编译中的连接步骤。

  类文件中的所有方法调用都在类文件中存储符号引用,而不是实际运行时内存布局中方法的入口地址,也就是前面的直接引用3360。

  这样Java就有了更强的动态扩展能力,同时Java方法调用过程也变得相对复杂。在类加载期间,甚至在运行时,确定目标方法的直接引用是必要的。

方法调用

方法调用中的所有目标方法都是类文件中常量池的引用。

 

  在类的加载和解析阶段,一些符号引用将被转换为直接引用:

  在程序实际执行之前有一个明确的方法调用版本,这个方法的调用版本在运行时是不能改变的。

  也就是说,在程序代码中完成调用目标时,编译器在编译时必须确定它,这也叫方法解析。

  00-1010在Java中,有两种方法符合‘编译时已知,运行时不可变’3360

  静态方法3360与类型直接相关,私有方法3360不能从外部访问。这两种方法的特点决定了它们都不能通过继承或其他方式重写,所以它们适合在类加载阶段进行解析。

  非虚方法3360将在类加载阶段将符号引用解析为该方法的直接引用。

  静态私有方法实例构造器父类方法虚拟方法3360在类加载阶段不会将符号引用解析为该方法的直接引用。

  除了以上非虚方法,其他方法都是虚方法。

  

方法解析

公共类StaticDispatch {静态抽象类Human {}静态类man扩展Human { }静态类Woman扩展Human { }公共静态void say Hello(Human guy){ system . out . println( Hello,Guy!);} public static void say Hello(Man guy){ system . out . println(您好,先生!);} public static void say Hello(woman guy){ system . out . println(你好,女士!);}公共静态void main(String[]args){ Human Man=new Man();人类女性=新女性();sayHello(男);sayHello(女);} }人类man=新人类();

 

  人是变量的静态类型。

  人是变量的实际类型。

  静态类型和实际类型都将释放程序3360中的变化

  静态类型:

  静态类型的改变只发生在被使用的时候,变量本身的静态类型是不会改变的。最终的静态类型在编译器中被称为实际类型3360。

  实际类型更改的结果是在运行时确定的。编译器在编译期间不知道对象的实际类型。Human human=新人类();sayHello(男);sayHello((男)男);//类型转换,静态类型改变,转换后的静态类型必须是man=new woman();//实际类型改变,实际类型不确定say hello(man);sayHello((女)男);//类型转换,静态类型更改。重载时,编译器通过参数的静态类型而不是实际类型进行判断。静态类型在编译时可以知道3360。

  在编译阶段,Javac编译器将根据参数的静态类型决定使用哪个重载版本。

  静态调度:

  所有依靠静态类型定位方法执行版本的调度动作典型应用3360方法重载静态调度都发生在编译阶段,所以确定静态调度的动作不是由虚拟机执行,而是由编译器完成。

  由于字面量不显示静态类型,它们只能通过语言规则来理解和推断。

  public class literal test { public static void say Hello(char arg){ system . out . println( Hello,char!);} public static void say Hello(int arg){ system . out . println( Hello,int!);} public static void say Hello(long arg){ system . out . println( Hello,long!);} public static void say hello(Character arg){

  System.out.println("Hello, Character!");}public static void main(String[] arg) {sayHello(a);}}编译器将重载方法从上向下依次注释,得到不同的输出

  如果编译器无法确定要自定转型为哪种类型,会提示类型模糊,拒绝编译

  

public class LiteralTest {public static void sayHello(String arg) {// 新增重载方法System.out.println("Hello, String!");}public static void sayHello(char arg) {System.out.println("Hello, char!");}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 main(String[] args) {Random r = new Random();String s = "abc";int i = 0;sayHello(r.nextInt() % 2 != 0 ? s : 1 );// 编译错误sayHello(r.nextInt() % 2 != 0 ? a : false);//编译错误}}

动态分派

public class DynamicDispatch {static abstract class Human {protected abstract void sayHello();}static class Man extends Human {@overrideprotected void sayHello() {System.out.println("Man Say Hello!");}}static class Woman extends Human {@overrideprotected void sayHello() {System.out.println("Woman Say Hello!");}}public static void main(String[] args) {Human man = new Man();Human women = new Woman();man.sayHello();woman.sayHello();man = new Woman();man.sayHello();}}

这里不是根据静态类型决定的

 

  静态类型的Human两个变量man和woman在调用sayHello() 方法时执行了不同的行为变量man在两次调用中执行了不同的方法导致这个现象的额原因 :这两个变量的实际类型不同

  Java虚拟机是如何根据实际类型分派方法的执行版本的: 从invokevirtual指令的多态查找过程开始 ,invokevirtual指令运行时解析过程大致分为以下几个步骤:

  找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C如果在类型C中找到与常量中的描述符和简单名称相符合的方法,然后进行访问权限验证,如果验证通过则返回这个方法的直接引用,查找过程结束;如果验证不通过,则抛出java.lang.illegalAccessError异常如果未找到,就按照继承关系从下往上依次对类型C的各个父类进行第二步的搜索和验证过程如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常

Java语言方法重写的本质:

invokevirtual指令执行的第一步就是在运行时期确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上

 

  这种在运行时期根据实际类型确定方法执行版本的分派过程就叫做动态分派

  

虚拟机动态分派的实现

虚拟机概念解析的模式就是静态分派和动态分派,可以理解虚拟机在分派中 "会做什么" 这个问题

 

  虚拟机 "具体是如何做到的" 在各种虚拟机实现上会有差别:

  由于动态分派是非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法因此在虚拟机的实际实现中,为了基于性能的考虑,大部分实现都不会真正的进行如此频繁的搜索最常用的"稳定优化"的方式是为类在方法区中建立一个虚方法表(Virtual Method Table,即vtable), 使用虚方法表索引代替元数据查找以提高性能虚方法表中存放着各个方法的实际入口地址:

  如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,都指向父类的实际入口如果子类中重写了这个方法,子类方法表中的地址将会替换为指向子类实际方法的入口地址具有相同签名的方法,在父类,子类的虚方法表中具有一样的索引序号:

  这样当类型变换时,仅仅需要变更查找的方法表,就可以从不同的虚方法表中按索引转换出所需要的入口地址

  方法表一般在类加载阶段的连接阶段进行初始化:

  准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕

  以上就是Java方法调用解析静态分派动态分派执行过程的详细内容,更多关于Java静态动态分派执行过程的资料请关注盛行IT其它相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: