详解java虚拟机方法调用_伯努力不努力的博客-程序员秘密

技术标签: java虚拟机  Android  

方法调用

方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。

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

在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能通过继承或别的方式重写其他版本,因此它们都适合在类加载阶段进行解析。

与之相对应的是,在Java虚拟机里面提供了5条方法调用字节码指令,分别如下。

invokestatic:调用静态方法。
invokespecial:调用实例构造器<init>方法、 私有方法和父类方法。
invokevirtual:调用所有的虚方法。
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

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

这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除去final方法)。

代码清单8-5 方法静态解析演示

/**
*方法静态解析演示
**
@author zzm
*/
public class StaticResolution{
    
public static void sayHello(){
System.out.println("hello world");
}
public static void main(String[]args){
StaticResolution.sayHello();
}}

使用javap命令查看这段程序的字节码,会发现的确是通过invokestatic命令来调用sayHello()方法的。

D:\Develop\>javap-verbose StaticResolution
public static void main(java.lang.String[]);
Code:
Stack=0,Locals=1,Args_size=1
0:invokestatic#31;//Method sayHello:()V
3return
LineNumberTable:
line 150
line 163

Java中的非虚方法除了使用invokestatic、 invokespecial调用的方法之外还有一种,就是被final修饰的方法。 虽然final方法是使用invokevirtual指令来调用的,但是由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,又或者说多态选择的结果肯定是唯一的。 在Java语言规范中明确说明了final方法是一种非虚方法。

解析调用一定是个静态的过程,在编译期间就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。 而分派(Dispatch)调用则可能是静态的也可能是动态的,根据分派依据的宗量数可分为单分派和多分派。 这两类分派方式的两两组合就构成了静态单分派、 静态多分派、 动态单分派、 动态多分派4种分派组合情况

分派

1.静态分派
代码清单8-6 方法静态分派演示

package org.fenixsoft.polymorphic;
/**
*方法静态分派演示
*@author zzm
*/
public class StaticDispatch{
    
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(Man guy){
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy){
System.out.println("hello,lady!");
}
public static void main(String[]args){
Human man=new Man();
Human woman=new Woman();
StaticDispatch sr=new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}}

运行结果:

hello,guy!
hello,guy!

在解决这个问题之前,我们先按如下代码定义两个重要的概念.

Human man=new Man();

我们把上面代码中的“Human”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。 例如下面的代码:

//实际类型变化
Human man=new Man();
man=new Woman();
//静态类型变化
sr.sayHello((Man)man)
sr.sayHello((Woman)man)

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。 静态分派的典型应用是方法重载。静态分派发生在编译阶段.

编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是“唯一的”,往往只能确定一个“更加合适的”版本。 这种模糊的结论在由0和1构成的计算机世界中算是比较“稀罕”的事情,产生这种模糊结论的主要原因是字面量不需要定义,所以字面量没有显式的静态类型,它的静态类型只能通过语言上的规则去理解和推断。

代码清单8-7 重载方法匹配优先级

package org.fenixsoft.polymorphic;
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(Serializable arg){
System.out.println("hello Serializable");
}
public static void main(String[]args){
sayHello('a');
}}

上面的代码运行后会输出:

hello char

这很好理解,’a’是一个char类型的数据,自然会寻找参数类型为char的重载方法,如果注释掉sayHello(char arg)方法,那输出会变为:

hello int

这时发生了一次自动类型转换,’a’除了可以代表一个字符串,还可以代表数字97(字符’a’的Unicode数值为十进制数字97),因此参数类型为int的重载也是合适的。 我们继续注释掉sayHello(int arg)方法,那输出会变为:

hello long

这时发生了两次自动类型转换,’a’转型为整数97之后,进一步转型为长整数97L,匹配了参数类型为long的重载。

不过实际上自动转型还能继续发生多次,按照char->int->long->float->double的顺序转型进行匹配。 但不会匹配到byte和short类型的重载,因为char到byte或short的转型是不安全的。

继续注释掉sayHello(long arg)方法,那输出会变为:

hello Character

这时发生了一次自动装箱,’a’被包装为它的封装类型java.lang.Character,所以匹配到了参数类型为Character的重载,继续注释掉sayHello(Character arg)方法,那输出会变为:

hello Serializable

出现hello Serializable,是因为java.lang.Serializable是java.lang.Character类实现的一个接口,当自动装箱之后发现还是找不到装箱类,但是找到了装箱类实现了的接口类型,所以紧接着又发生一次自动转型。

char可以转型成int,但是Character是绝对不会转型为Integer的,它只能安全地转型为它实现的接口或父类。 Character还实现了另外一个接口java.lang.Comparable<Character>,如果同时出现两个参数分别为Serializable和Comparable<Character>的重载方法,那它们在此时的优先级是一样的。

程序必须在调用时显式地指定字面量的静态类型,如:sayHello((Comparable<Character>)’a’),才能编译通过。 下面继续注释掉sayHello(Serializable arg)方法,输出会变为:

hello Object

这时是char装箱后转型为父类了,如果有多个父类,那将在继承关系中从下往上开始搜索,越接近上层的优先级越低。 即使方法调用传入的参数值为null时,这个规则仍然适用。我们把sayHello(Object arg)也注释掉,输出将会变为:

hello char……

解析与分派这两者之间的关系并不是二选一的排他关系,它们是在不同层次上去筛选、 确定目标方法的过程。 例如,前面说过,静态方法会在类加载期就进行解析,而静态方法显然也是可以拥有重载版本的,选择重载版本的过程也是通过静态分派完成的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012124438/article/details/79342009

智能推荐

pandas常用操作_桥豆麻袋XQXQXQ的博客-程序员资料

python 数据分析学习笔记(二)基于pandas的数据清洗和数据操作1.处理丢失数据丢失数据类型:nonenp.nan(NaN)type(None) ##对象类型不可以参与运算type(np.nan) #浮点型数据可以参与计算在pandas中如果遇到None形式的空值,则pandas会自动转化成Nan形式处理空值的方法isnull+anynotnull+alldata=DataFrame(data=np.random.randint(1,100,size=(7,5))

HttpClient访问https,设置忽略SSL证书验证_衣兜里的博客-程序员资料_httpclient 忽略ssl

报错:sun.security.validator.ValidatorException:PKIXpathbuildingfailed:sun.security.provider.certpath.SunCertPathBuilderException:unabletofindvalidcertificationpathtorequestedtargetimport java.security.cert.CertificateException;import java...

浅谈两轮平衡车的控制原理(续)_吾理小子的博客-程序员资料_两轮平衡车控制原理

前言:上次云里雾里的说了一通,不知道对平衡车的控制有没有说到点子上。单纯的讲解原理可能会很无聊,但是作为一个技术宅来说,就算头皮发麻也要接着看下去。哈哈,吾理小子争取用通俗的语言把自己懂的知识讲解出来。好了,闲话少说,进入正题。上文已经做好了平衡车站立起来的全部准备工作,接下来就是控制的核心了,如果对上面讲到的内容还没有看到,建议先看上一篇,否则会有莫名其妙的感觉。首先,说说陀螺仪的安装位...

springcloud——hystrix图形化dashboard服务监控_weixin_43925059的博客-程序员资料

监控模块与被监控服务必须添加的图形化依赖: <!--springboot框架web项目起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

摇一摇(根据传感器和加速器实现)_qq_45480938的博客-程序员资料

摇一摇(根据传感器和加速器实现)mainactivitypackage com.example.myyaoyiyaoimport android.content.Contextimport android.content.Intentimport android.hardware.Sensorimport android.hardware.SensorEventimport and...

robotframework 自动化测试 (一)环境搭建_蘑菇ding的博客-程序员资料

最近开始接触编写自动化测试用例,要搭建 robot framework 环境,配置了将近一天,记录以备以后参考:环境:win10首先列出要下载安装的一些东西:1、安装pythonhttps://www.python.org/ 双击运行,选好路径直接安装就OK(我的路径D:\Python27)...

随便推点

windows事件查看器_牛板筋不筋的博客-程序员资料_windows事件查看器

打开方式:右键左下角的windows ,然后按v                        或者 Windows+R 后 输入eventvwr.msc 也行主要看:Windows 日志——系统,这里会有一些你程序出现问题的日志,其他地方并没有太大作用右键最近一个日志,并点击事物属性,如下图出错原因这个就是我Tor Browser 连接不上的原因 问题:w...

新高考计算机学业水平考试,解密新高考——学业水平考试_咔咔伊的博客-程序员资料

原标题:解密新高考——学业水平考试新高考本质上是普通高校考试招生录取制度的改革,目的是根据“两依据,一参考”的标准,形成综合评价、多元录取考试招生格局。其中,“两依据”就是依据高考成绩和学业水平考试成绩,“参考”就是将高中学生的综合素质评价档案作为录取的参考。其中学业水平考试提到了前所未有的新高度,也是高中学生需要引起重视的考试之一。 一、什么是学业水平考试学业水平考试是用来衡量学生各个科目学习情...

linux启动界面中的initrd,在Linux系统上开启Initrd文件系统的方法_weixin_39850143的博客-程序员资料

initial RAM diskLinux初始RAM磁盘(initrd)是在系统引导过程中挂载的一个临时根文件系统,用来支持两阶段的引导过程。initrd文件中包含了各种可执行程序和驱动程序,它们可以用来挂载实际的根文件系统,然后再将这个 initrd RAM磁盘卸载,并释放内存。在很多嵌入式Linux系统中,initrd 就是最终的根文件系统。本文将探索 Linux 2.6 的初始 RAM磁盘,...

使用DatagramSocket发送、接收数据(Socket之UDP套接字)_jiangxinyu的博客-程序员资料_datagramsocket

http://book.51cto.com/art/201203/322540.htm17.4.2 使用DatagramSocket发送、接收数据(1)Java使用DatagramSocket代表UDP协议的Socket,DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报,Java使用DatagramPacket来代表数据报,Datagr

【Cortex-M0】19.8欠压电压值设定(BOD)_史提芬温的博客-程序员资料

NuMicro M051系列微控制器本身有对系统电压进行检测的功能,一旦系统电压低于设定的门限电压后,将自动停止正常运行,并可设置进入复位状态。当系统电压稳定恢复到设定的门限电压之上,将再次启动运行,即相当于一次掉电再上电的复位。作为一个正式的系统或产品,当系统基本功能调试完成后,一旦进行现场测试阶段,请注意马上改写芯片的配置位,启动内部欠压电压检测功能。NuMicro M051系列微控制器支

DPDK2.2.0开发杂记一—— 网口抓包分片禁止及MTU配置_zangchang的博客-程序员资料

1. 禁止网口抓包分片        DPDK收发包是基础核心模块,网卡需要应用进程进行配置并启动,测试过程中发现DPDK驱动igb_uio抓包可能会出现mbuf串。当网络包比较大时,DPDK驱动会把包进行分片放到一组Mbuf中并进行链接成串,应用进程从接收队列中取出的可能就是Mbuf串,如果要进行深层解析需要应用进程自己进行重组,给应用进程造成负担。在实际应用中我们可以增大Mbuf大小禁止掉DP...

推荐文章

热门文章

相关标签