JVM指令:方法调用之解析调用

文章目录

    • 介绍-方法调用指令
    • 1.invokestatic
      • 静态方法测试类
      • 反编译字节码
    • 2.invokespecial
      • 特殊方法测试类
      • 反编译字节码
    • 3.invokeinterface
      • 接口方法测试类
      • 反编译字节码
    • 4.invokedynamic
      • 动态调用方法测试类
      • 反编译字节码
    • 5.invokevirtual
      • 虚方法测试类
      • 反编译字节码

介绍-方法调用指令

首先要清楚的是,在Java中,有关字节码文件的编译过程不包含传统程序语言编译的连接步骤,一切方法调用在字节码文件中只是符号引用,而不是在方法在实际运行时内存布局中的入口地址,也就是直接引用。

在上一篇文章《探索JVM类加载机制》中,分析了在类加载的解析阶段,会将一部分的符号引用转化为直接引用,这种解析能够成立的前提条件就是:方法在程序真正运行之前就有一个可以确定的调用版本,并且这个方法的调用版本在运行期间是不可变的,也就是该方法调用具有唯一的目标方法。从程序的角度而言,调用目标在程序代码写好,编译器进行编译的那一刻就已经确定下来了,这类方法的调用称之为解析。

在《Java虚拟机规范》中有以下五种方法调用字节码指令:

  1. invokestatic:用于调用静态方法。
  2. invokespecial:用于调用实例构造器<init>(),私有方法和父类中的方法。
  3. invokeinterface:用于调用接口方法,会在运行时确定一个实现该接口的对象。
  4. invokedynamic:在运行时动态解析初调用点限定符所引用的方法,然后在执行该方法。
  5. invokevirtual:用于调用所有的虚方法。

下面将会演示以上五种指令的实际应用,在介绍之前,需要借助Java开发工具包(JDK)中提供的一个命令行工具 javap,用于反编译Java字节码,以便深入了解和分析已编译的Java类文件。指令如下,有关其详细介绍内容,读者可从网上查阅,在此不赘述。

# -verbose 会显示与指定类相关的字节码指令、常量池、方法、字段和其他类信息。
javap -verbose [字节码文件的路径]

1.invokestatic

静态方法测试类

public class StaticMethodCallInstruction {public static void main(String[] args) {int num = getNum(1); // 根据下方反编译结果可知,此处会生成一个invokestatic指令用于调用Method:getNum:(I)ISystem.out.println(num);}// 静态方法,该方法编译后简单名称为getNum,描述符为(I)I,根据上一篇文章所述,在进行方法搜索时会匹配到该方法。public static int getNum(int i){ return i;}}

反编译字节码

# 执行命令
javap -verbose /target/classes/com/mytest/project/method/call/StaticMethodCallInstruction.class
# 以下仅展示主要内容,并会简单介绍一下相关内容,其他与本次案例无关的内容将省略# 常量池,表类型结构
Constant pool:#2 = Methodref          #5.#27         // com/mytest/project/method/call/StaticMethodCallInstruction.getNum:(I)I#3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;#4 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V#5 = Class              #32            // com/mytest/project/method/call/StaticMethodCallInstruction#27 = NameAndType       #21:#22        // getNum:(I)I#28 = Class              #34            // java/lang/System#29 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;#30 = Class              #37            // java/io/PrintStream#31 = NameAndType        #38:#39        // println:(I)V#32 = Utf8               com/mytest/project/method/call/StaticMethodCallInstruction# main方法
public static void main(java.lang.String[]);# 描述符descriptor: ([Ljava/lang/String;)V# 访问标记flags: (0x0009) ACC_PUBLIC, ACC_STATIC# 方法属性表Code:# 操作数栈深度,局部变量表的大小,方法参数表的大小stack=2, locals=2, args_size=10: iconst_11: invokestatic  #2                  // Method getNum:(I)I4: istore_15: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;8: iload_19: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V12: return# 行号映射表(源码与code属性中的索引)LineNumberTable:line 12: 0line 13: 5line 14: 12# 局部变量表LocalVariableTable:Start  Length  Slot  Name   Signature0      13     0  args   [Ljava/lang/String;5       8     1   num   I# 方法参数表MethodParameters:Name                           Flagsargs# getNum方法public static int getNum(int);descriptor: (I)Iflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=1, locals=1, args_size=10: iload_01: ireturnLineNumberTable:line 17: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       2     0     i   IMethodParameters:Name                           Flagsi

2.invokespecial

特殊方法测试类

//父类
public class ParentMethod {public void parentMethod(){return;}}// 子类
public class SpecialMethodCallInstruction extends ParentMethod {// 无参构造器public SpecialMethodCallInstruction(){}public void demo() {// 调用实例构造器方法new SpecialMethodCallInstruction(); // class com/mytest/project/method/call/SpecialMethodCallInstruction// 调用私有方法test(); // Method test:()V// 调用父类方法super.parentMethod(); // Method com/mytest/project/method/ParentMethod.parentMethod:()V// 该方法在编译时会被认为时虚方法,需要显示调用父类方法,才会执行invokespecialparentMethod(); // Method parentMethod:()V}// 私有方法private void test(){return;}}

反编译字节码

 public void demo();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=2, locals=1, args_size=10: new           #2                  // class com/mytest/project/method/call/SpecialMethodCallInstruction3: dup4: invokespecial #3                  // Method "<init>":()V7: pop8: aload_09: invokespecial #4                  // Method test:()V12: aload_013: invokespecial #5                  // Method com/mytest/project/method/ParentMethod.parentMethod:()V16: aload_017: invokevirtual #6                  // Method parentMethod:()V20: returnLineNumberTable:line 18: 0line 20: 8line 22: 12line 24: 16line 25: 20LocalVariableTable:Start  Length  Slot  Name   Signature0      21     0  this   Lcom/mytest/project/method/call/SpecialMethodCallInstruction;

3.invokeinterface

接口方法测试类

public class InterfaceMethodCallInstruction implements InterfaceMethod {public void demo(){InterfaceMethod interfaceMethod = new InterfaceMethodCallInstruction();// 调用接口方法interfaceMethod.iMethod(); // com/mytest/project/method/call/intf/InterfaceMethod.iMethod:()V}@Overridepublic void iMethod() {return;}
}

反编译字节码

 public void demo();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=2, locals=2, args_size=10: new           #2                  // class com/mytest/project/method/call/InterfaceMethodCallInstruction3: dup4: invokespecial #3                  // Method "<init>":()V7: astore_18: aload_19: invokeinterface #4,  1            // InterfaceMethod com/mytest/project/method/call/intf/InterfaceMethod.iMethod:()V14: returnLineNumberTable:line 14: 0line 15: 8line 16: 14LocalVariableTable:Start  Length  Slot  Name   Signature0      15     0  this   Lcom/mytest/project/method/call/InterfaceMethodCallInstruction;8       7     1 interfaceMethod   Lcom/mytest/project/method/call/intf/InterfaceMethod;

4.invokedynamic

invokedynamic 指令用于在运行时动态确定要调用的方法。与传统的 invokevirtual、invokeinterface 等指令不同,invokedynamic 指令将查找目标方法的决定权从虚拟机转嫁到具体用户代码之中,每一处含有invokedynamic指令的位置都称做“动态调用点”(Dynamic Call Site),这条指令的第一个参数不再是代表方法符号引用的CONSTANT_Methodref_info常量,而是变为JDK 1.7新加入的CONSTANT_InvokeDynamic_info常量,从这个新常量中可以得到3项信息:引导方法(Bootstrap Method,此方法存放在新增的BootstrapMethods属性中)、方法类型(MethodType)和名称。

引导方法是 invokedynamic 指令的核心部分,它负责在运行时生成和链接要调用的方法。引导方法是一个普通的Java方法,但它具有特殊的签名和参数。引导方法的参数包括:

  • 一个查找器(Lookup)对象,用于查找和创建方法句柄、构造函数句柄等;
  • 一个名称(Name)对象,表示要调用的方法的名称;
  • 一个方法类型(MethodType)对象,表示要调用的方法的参数类型和返回类型;
  • 可变数量的静态参数,这些参数是传递给引导方法的附加信息,用于生成和链接目标方法。

引导方法的主要任务是根据这些参数动态生成和链接目标方法。生成目标方法的过程可以是通过反射API查找已存在的方法,也可以是动态生成新的方法(例如,使用ASM库或Java编译器API)。链接目标方法的过程是将生成的目标方法与invokedynamic指令关联起来,以便在运行时正确调用。

当JVM执行到 invokedynamic 指令时,它会首先查找与该指令关联的引导方法。引导方法根据传入的参数动态生成和链接目标方法,并返回一个称为“调用站点”(CallSite)的对象。调用站点对象封装了目标方法的所有信息,包括方法句柄、参数类型和返回类型等。

一旦调用站点对象被创建并返回给 invokedynamic 指令,JVM就会将该指令与调用站点对象关联起来。在后续的执行过程中,当再次遇到相同的invokedynamic指令时,JVM会直接通过调用站点对象调用目标方法,而无需再次执行引导方法。这种机制可以显著提高动态方法调用的性能。

以上说明内容参考链接:https://blog.csdn.net/li371518473/article/details/136646069

动态调用方法测试类

public class DynamicMethodCallInstruction {public void demo(){Thread thread = new Thread(() -> {int i = 0;i++;});thread.run();}}

反编译字节码

public void demo();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=3, locals=2, args_size=10: new           #2                  // class java/lang/Thread3: dup4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable; // 其中#0表示#BootstrapMethods属性表的第0项9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V12: astore_113: aload_114: invokevirtual #5                  // Method java/lang/Thread.run:()V17: returnLineNumberTable:line 14: 0line 18: 13line 19: 17LocalVariableTable:Start  Length  Slot  Name   Signature0      18     0  this   Lcom/mytest/project/method/call/DynamicMethodCallInstruction;13       5     1 thread   Ljava/lang/Thread;# BootstrapMethods属性
BootstrapMethods:# LambdaMetafactory.metafactory会生成一个实现了函数式接口的匿名内部类,并实例化,然后委托给MethodHandle返回一个CallSite对象。该CallSite类型的对象实现对目标方法也就是run()的调用0: #26 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;Method arguments:#27 ()V#28 REF_invokeStatic com/mytest/project/method/call/DynamicMethodCallInstruction.lambda$demo$0:()V#27 ()V

5.invokevirtual

在Java 语言能在解析阶段中唯一确定其调用版本的方法有静态方法、私有方法、实例构造器、父类方法以及被final修饰的方法,这5种方法会在类加载的时候就可以把符号引用解析为该方法的直接引用(入口地址)。这些方法统称为“非虚方法”,相反,其他方法被称之为“虚方法”。

所以通常认为,虚方法是指在类加载阶段(这里指的是解析阶段)都不能确定方法调用的直接引用,而只有在运行时才能确定的方法。

由于历史设计的原因,被 final修饰的实例方法是使用 invokevirtual 指令来调用的,但是因为其无法被覆盖,没有其他版本的可能,所以方法接受者也不需要进行多态选择,又或者说多态选择的结果是唯一的。所以,在《Java语言规范》中明确定义了被 final修饰的方法是一种非虚方法。

虚方法测试类

public class VirtualMethodCallInstruction {public void demo(){// 调用final修饰的实例方法,实际上是非虚方法,但是指令为invokevirtualfMethod();// 调用final修饰的类方法,指令为invokestaticsfMethod();}public final void fMethod(){return;}public static final void sfMethod(){return;}}

反编译字节码

public void demo();descriptor: ()Vflags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokevirtual #2                  // Method fMethod:()V4: invokestatic  #3                  // Method sfMethod:()V7: returnLineNumberTable:line 13: 0line 15: 4line 16: 7LocalVariableTable:Start  Length  Slot  Name   Signature0       8     0  this   Lcom/mytest/project/method/call/VirtualMethodCallInstruction;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/803811.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Kubernetes集群:master主节点初始化]:通过Calico和Coredns网络插件方式安装

文章目录 前置&#xff1a;Docker和K8S安装版本匹配查看0.1&#xff1a;安装指定docker版本 **[1 — 7] ** [ 配置K8S主从集群前置准备操作 ]一&#xff1a;主节点操作 查看主机域名->编辑域名->域名配置二&#xff1a;安装自动填充&#xff0c;虚拟机默认没有三&#xf…

程序员如何搞副业?在线教育!

程序员如何搞副业&#xff1f; 程序员不仅拥有将抽象概念转化为实际应用的能力&#xff0c;还通常具备强大的逻辑思维和问题解决能力。然而&#xff0c;许多程序员并不满足于仅仅在一家公司工作&#xff0c;他们渴望通过副业来实现个人价值的最大化&#xff0c;增加收入&#…

深度学习-多尺度训练的介绍与应用

一、引言 在当今快速发展的人工智能领域&#xff0c;多尺度训练已经成为了一种至关重要的技术&#xff0c;特别是在处理具有复杂结构和不同尺度特征的数据时。这种技术在许多应用中发挥着关键作用&#xff0c;例如图像识别、自然语言处理和视频分析等。 多尺度训练的定义 多尺…

使用C++语言构建基于mysql API的统一数据库访问接口

文章目录 基本设计理念mysql的基本API基本的demo示例自增的处理架构设计代码DataRecord接口DataRecord代码TABLE A数据结构类DBAdaptermysql Adapterbusiness层在java这种web领域的后台语言里,操作数据库的框架已经炉火纯青,非常的成熟,包括了关系型数据库和非关系型等。 关…

「44」直播间换脸,揭开神秘的面纱……

「44」换脸神器 让你瞬间秒变「明星脸」带货 DeepFace是Facebook的人脸识别系统之一&#xff0c;旨在在照片和视频中准确识别和标识人脸。它使用深度学习和神经网络技术来进行高度精确的人脸匹配和验证。 DeepFace利用了大量的训练数据和先进的人脸识别算法&#xff0c;能够…

小程序中展示富文本 图片不适配?视频不显示?

最近遇到一个问题在小程序中渲染富文本的内容&#xff0c;如果里面有图片和视频&#xff0c;渲染的时候图片大小超屏幕了&#xff0c;而视频完全没有显示&#xff01;&#xff01;&#xff01; 最后通过正则匹配替换后 图片可以了视频还是不行&#xff0c;看了微信小程序api官…

react项目中需要条形码功能,安装react-barcode使用时报错

react项目中需要条形码功能&#xff0c;用yarn add安装react-barcode后&#xff0c;在项目中使用import Barcode from ‘react-barcode’&#xff0c;页面中一直白屏&#xff0c;加载中 查看控制台报以下错误 load component failed Error: Module "./react-barcode"…

Word 画三线表模板---一键套用

1、制作三线表 1&#xff09;设置为无边框 选中表格&#xff0c;点击「右键」——「边框」——「无框线」。 2&#xff09;添加上下边框线 选中表格后&#xff0c;点击【右键】——【表格属性】——【边框和底纹】&#xff0c;边框线选择【1.5磅】&#xff0c;然后点击【上框…

【数组】【最长距离】使循环数组所有元素相等的最少秒数

本文涉及知识点 数组 最长距离 LeetCode2808. 使循环数组所有元素相等的最少秒数 给你一个下标从 0 开始长度为 n 的数组 nums 。 每一秒&#xff0c;你可以对数组执行以下操作&#xff1a; 对于范围在 [0, n - 1] 内的每一个下标 i &#xff0c;将 nums[i] 替换成 nums[i] …

react17+18 中 setState是同步还是异步更新

在类组件中使用setState&#xff0c;在函数式组件中使用hooks的useState。 setstate目录 1. 类组件1.1 react 17版本1.2 react 18版本 2、函数式组件 1. 类组件 1.1 react 17版本 参考内容&#xff1a;第十一篇&#xff1a;setState 到底是同步的&#xff0c;还是异步的&…

【BUG】element-ui表格中使用video标签,数据翻页,video中的视频仍然显示第一页的视频,没有重新加载

BUG描述 遇到一个问题&#xff0c;使用element-ui构建的管理端后台&#xff0c;表格里面每一行都有一个video标签&#xff0c;里面有视频&#xff0c;当我翻页了以后&#xff0c;视频不会重新加载&#xff0c;仍然显示的是第一页的视频&#xff0c;代码如下&#xff1a; <e…

24.Linux下程序调试分析工具`Valgrind`

Linux下程序调试分析工具Valgrind 文章目录 Linux下程序调试分析工具Valgrind1.基本介绍2.下载编译安装Valgrind C程序内存扫描示例reference 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 1.基本介绍 Valgrind是开源的Linux程序调试工…

Openstack(T)openstack event list 验证panko,显示为空

部署完panko,通过执行openstack event list 显示为空 表示没有获取到事件数据 排错思路: 1.保障ceilometer panko服务都是正常的 2.保障ceilometer 到 panko 的数据传输&#xff0c;是正确配置从采集点到存储点的 下面展示配置详情&#xff0c;执行openstack event list 事…

ROS中不同情况下配置文件的编写

配置文件 话题中自定义msg1. 编写msg2. 编辑package.xml3. 编辑CMakeLists.txt4. 编译5. 使用5. 在其他功能包中调用msg5.1 编辑package.xml5.2 编辑CMakeLists.txt 服务中自定义srv1. 编写srv2. 其他 调用头文件1. 编写头文件2. (可选&#xff0c;为了编程时有代码提示)3. 编写…

opencv+python(二值化图像)

1、全局二值化&#xff1a;将图像全部变成两种值&#xff0c;比如&#xff1a;0&#xff0c;255 threshold(src: ndarray&#xff08;图像&#xff0c;最好是灰度图&#xff09; thresh: float,&#xff08;阙值&#xff09; maxval: float,&…

function 包装器 ——C++新特性(二)

文章目录 包装器function包装器可以包装可调用对象其他使用示例 &#x1f396; 博主的CSDN主页&#xff1a;Ryan.Alaskan Malamute &#x1f4dc; 博主的代码仓库主页 [ Gitee ]&#xff1a;ryanala [GitHub]&#xff1a; Ryan-Ala 包装器 function 使用 function包装器…

身份证二、三、四要素实名认证接口、C#开发示例

在瞬息万变的网络时空&#xff0c;每一次点击&#xff0c;每一份交易都离不开对“你是谁”的确认。想象一下&#xff0c;当我们畅游在网络的海洋中&#xff0c;无论是注册心仪已久的APP还是在线办理重要的业务&#xff0c;甚至是在虚拟世界中结交新朋友等活动时&#xff0c;只需…

Selenium+Chrome Driver 爬取搜狐页面信息

进行selenium包和chromedriver驱动的安装 安装selenium包 在命令行或者anaconda prompt 中输入 pip install Selenium 安装 chromedriver 先查看chrome浏览器的版本 这里是 123.0.6312.106 版 然后在http://npm.taobao.org/mirrors/chromedriver/或者https://googlechrom…

EasyPOI复杂表格导入

EasyPOI复杂表格导入 多表头数据导入方式一导入表格实体类文件导入代码测试结果 方式二导入表格实体类文件导入代码测试结果 总结 设置表格从哪行读取表格内容 多表头数据导入 方式一 导入的表格样式如下 导入表格实体类 package com.demo.entity;import cn.afterturn.eas…

基于令牌桶算法对高并发接口的优化

业务背景 项目中有一个抽奖接口&#xff0c;此接口需要处理高并发问题以及使用脚本作弊的问题。 本文主要探讨如何最大程度地减少脚本作弊行为对抽奖业务的影响。 设计思路 如何减少脚本作弊行为对抽奖业务的影响 使用令牌桶算法&#xff0c;对频率过高的用户请求进行拦截 …