Java方法调用解析静态分派动态分派执行过程

方法调用

在程序运行时,进行方法调用是最普遍,最频繁的操作

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

  • 方法调用阶段唯一的任务就是确定被调用的方法版本,即调用哪一个方法
  • 不涉及方法内部的具体运行过程

Class文件的编译过程不包括传统编译中的连接步骤

Class文件中的一切方法调用在Class文件里面存储的都是符号引用,而不是方法在在实际运行时内存布局中的入口地址,即之前的直接引用:

  • 这样使得Java具有更强大的动态扩展能力
  • 同时也使得Java方法调用过程变得相对复杂
  • 需要在类加载期间,甚至会到运行期间才能确定目标方法的直接引用

方法解析

所有方法调用中的目标方法在Class文件里都是一个常量池的引用

在类的加载解析阶段,会将其中的一部分符号引用转化为直接引用:

方法在程序真正执行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的

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

Java方法分类

在Java中符合 "编译期可知,运行期不可变" 的方法有两大类:

  • 静态方法: 与类型直接关联
  • 私有方法: 在外部不可被访问

这两种方法各自的特点决定这两种方法都不可能通过继承或者别的方式重写版本,因此适合在类加载阶段进行解析

非虚方法: 在类加载阶段会把符号引用解析为该方法的直接引用

  • 静态方法
  • 私有方法
  • 实例构造器
  • 父类方法

虚方法: 在类加载阶段不会将符号引用解析为该方法的直接引用

除去以上的非虚方法,其它的方法均为虚方法

静态分派

public class StaticDispatch {
	static abstract class Human {
	}
	static class Man extends Human {
	}
	static class Woman extends Human {
	}
	public static void sayHello(Human guy) {
		System.out.println("Hello, Guy!");
	}
	public static void sayHello(Man guy) {
		System.out.println("Hello, Gentleman!");
	}
	public static void sayHello(woman guy) {
		System.out.println("Hello, Lady!");
	}
	public static void main(String[] args) {
		Human man = new Man();
		Human women = new Woman();
		sayHello(man);
		sayHello(woman);
	}
}

Human man = new Human();

Human为变量的静态类型

Man为变量的实际类型

静态类型和实际类型在程序中都会放生变化:

静态类型:

  • 静态类型的变化仅仅在使用时发生
  • 变量本身的静态类型不会被改变
  • 最终的静态类型在编译器中可知

实际类型:

  • 实际类型变化的结果在运行期才确定下来
  • 编译器在编译期间并不知道一个对象的实际类型是什么
Human human = new Man();
sayHello(man);
sayHello((Man)man);		// 类型转换,静态类型变化,转型后的静态类型一定是Man
man = new woman();		// 实际类型变化,实际类型是不确定的
sayHello(man);
sayHello((Woman)man);	// 类型转换,静态类型变化

编译器在重载时是通过参数的静态类型而不是实际类型作为判断依据,静态类型在编译期间可以知道:

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

静态分派:

  • 所有依赖静态类型来定位方法的执行版本的分派动作
  • 典型应用 :方法重载

静态分派发生在编译阶段,因此确定静态分派的的动作不是由虚拟机执行的,而是由编译器完成的

由于字面量没有显示静态类型,只能通过语言上的规则去理解和推断

public class LiteralTest {
	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[] 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 {
		@override
		protected void sayHello() {
			System.out.println("Man Say Hello!");
		}
	}
	static class Woman extends Human {
		@override
		protected 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两个变量manwoman在调用sayHello() 方法时执行了不同的行为
  • 变量man在两次调用中执行了不同的方法

导致这个现象的额原因 :这两个变量的实际类型不同

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

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

Java语言方法重写的本质:

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

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

虚拟机动态分派的实现

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

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

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

虚方法表中存放着各个方法的实际入口地址:

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

具有相同签名的方法,在父类,子类的虚方法表中具有一样的索引序号:

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

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

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

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

时间: 2022-06-22

Java 静态绑定与动态绑定深入分析

Java 静态绑定与动态绑定           最近学习java 的知识,对java的静态绑定和动态绑定的学习很是模糊不清,然后就百度一下对其相应的知识进行了总结整理,帮助掌握这部分的知识 程序绑定的概念: 绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来.对java来说,绑定分为静态绑定和动态绑定:或者叫做前期绑定和后期绑定. 静态绑定: 在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现.例如:C. 针对jav

代理模式:JAVA静态代理和动态代理的实例和实现详解

目录 前言 静态代理 实现简述 创建human接口 创建接口实现类 创建针对接口实现增强操作的代理 代理实现效果 动态代理 实现简述 要点:向上转型 创建YoungMan接口 创建两个接口实现类 创建动态代理实例对象 代理实现效果 要点:InvocationHandler补充 代理模式和修饰模式的区别 总结 前言 代理模式,我们这里结合JAVA的静态代理和动态代理来说明,类比Spring AOP面向切面编程:增强消息,也是代理模式. 而我们的静态代理和动态代理,与(service)接口和(ser

JVM 方法调用之动态分派(详解)

1. 动态分派 一个体现是重写(override).下面的代码,运行结果很明显. public class App { public static void main(String[] args) { Super object = new Sub(); object.f(); } } class Super { public void f() { System.out.println("super : f()"); } public void f(int i) { System.out

java JVM方法分派模型静态分派动态分派全面讲解

前言 了解 行为方法分派 有利于在行为分派时时进行一些功能操作 本文全面讲解行为分派的类型:静态 & 动态行为分派,希望你们会喜欢. 目录 1. 知识储备 1.1 分派 定义:确定执行哪个方法 的过程 a. 疑问 有些读者会问,方法的执行不是取决于代码设置中的执行对象吗?为什么还要选择呢? b. 回答 若 一个对象对应于多个方法 时,就需要进行选择了 读者应该都想到了 Java中的特性:多态,即重写 & 重载.下面我会详细讲解. 分类:静态分派 & 动态分派.下面我将详细讲解. 1

Java的动态分派和静态分派的实现

Java 方法执行时的动态分派和静态分派是 Java 实现多态的本质 背景 Java 的动态分派和静态分派也是 Java 方法的执行原理. Java 源代码的编译之后,方法之间的调用是使用符号引用来表示的.当字节码被 JVM 加载之后,符号引用才会被替换为对应方法在方法区的真实内存地址.那么在替换之前,由于 Java 的方法重写.重载,就导致符号引用对应的方法可能是一个虚方法,那么方法的真实实现在运行时就可能有多个. 所以在将符号引用替换为真实地址时,还需要做一件事情:那就是确定符号引用要替换的

使用java将动态网页生成静态网页示例

复制代码 代码如下: package com.tools;import java.io.*;import java.net.URL; /** * Title:动态页面静态化 */public class GoToHtml {/** *  * @param page *            存放静态页面的本地文件路径(c,d,e,f,g) * @param url_addr *            所要生成的静态页的URL地址(http://) * @return */public boole

JVM 方法调用之静态分派(详解)

分派(Dispatch)可能是静态也可能是动态的,根据分派依据的宗量数可分为单分派和多分派.这两种分派方式的两两组合就构成了静态单分派,静态多分派,动态单分派,动态多分派这4种组合.本章讲静态分派. 1.静态分派 所有依赖静态类型来定位方法执行版本的分派动作称为静态分派.静态分派的典型应用是方法重载.静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的. 那么什么是静态类型(static type)呢? Super object = new Sub(); 像上面的语句,Sup

Java JDK动态代理(AOP)的实现原理与使用详析

本文主要给大家介绍了关于Java JDK动态代理(AOP)实现原理与使用的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 代理模式UML图: 简单结构示意图: 为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别.通过代理类这中间一层,能有效控制对委托类对

详解Java JDK动态代理

今天来看看Java的另一种代理方式--JDK动态代理 我们之前所介绍的代理方式叫静态代理,也就是静态的生成代理对象,而动态代理则是在运行时创建代理对象.动态代理有更强大的拦截请求功能,因为可以获得类的运行时信息,可以根据运行时信息来获得更为强大的执(骚)行(操)力(作). 我们还是以上一个例子为例,这里的IStars接口和Stars类都不需要修改,只需要修改代理类. 创建JDK动态代理需要先实现InvocationHandler接口,并重写其中的invoke方法,具体步骤如下: 1. 创建一个类

Java 8 动态类型语言Lambda表达式实现原理解析

Java 8支持动态语言,看到了很酷的Lambda表达式,对一直以静态类型语言自居的Java,让人看到了Java虚拟机可以支持动态语言的目标. import java.util.function.Consumer; public class Lambda { public static void main(String[] args) { Consumer<String> c = s -> System.out.println(s); c.accept("hello lambd

JSP技术实现动态页面到静态页面的方法

本文是介绍了jsp技术实现动态页面到静态页面的方法,分享给大家,具体如下: 对于JSP技术实现动态页面到静态页面的方案,我们从三个步骤来说明: JSP技术实现动态页面到静态页面的方案第一: 为了能深入浅出的理解这个框架的由来,我们首先来了解一下JSP解析器将我们写的JSP代码转换成的JAVA文件的内容. 下面是一个JSP文件test.jsp ﹤%@ page language=java contentType=text/html;charset=GB2312 %﹥ ﹤% out.write(﹤!

JSP页面的动态包含和静态包含示例及介绍

一.静态包含 本文介绍JSP静态包含语句,即使用JSP的include指令来完成的包含操作. JSP中,有两种包含其它文件的方式,一种是静态包含,一种是动态包含.本文介绍其中的静态包含. 所谓静态包含,跟ASP中的包含是一样的效果,即将所有包含的文件,先静态的包含进来,组成一个大的JSP文件,然后再经过编译器统一编译,生成HTML代码. JSP静态包含的语句为: 复制代码 代码如下: <%@ include file="file.jsp" %> 这里的file.jsp就是要

实例讲解Java中动态代理和反射机制

反射机制 Java语言提供的一种基础功能,通过反射,我们可以操作这个类或对象,比如获取这个类中的方法.属性和构造方法等. 动态代理:分为JDK动态代理.cglib动态代理(spring中的动态代理). 静态代理 预先(编译期间)确定了代理者与被代理者之间的关系,也就是说,若代理类在程序运行前就已经存在了,这种情况就叫静态代理 动态代理 代理类在程序运行时创建的代理方式.也就是说,代理类并不是在Java代码中定义的,而是在运行期间根据我们在Java代码中的"指示"动态生成的. 动态代理比

详解Java Cglib动态代理

今天来介绍另一种更为强大的代理--Cglib动态代理. 什么是Cglib动态代理? 我们先回顾一下上一篇的jdk动态代理,jdk动态代理是通过接口来在运行时动态创建委托类的代理对象,但是跟静态代理一样有一个缺点,就是必须和委托类实现相同的接口,当接口数量增加时,便需要增加代理类的数量才能满足需求,而且如果委托类是别人写的,而且没有实现任何接口,那么jdk动态代理就有些力不从心了. 这时候Cglib动态代理就脱颖而出了,Cglib并不依赖接口,可以直接生成委托类的代理对象,而且可以代理委托类的任意