Java虚拟机4 方法调用原理、动态类型支持

Java虚拟机 系列文章

Java虚拟机1 内存管理、GC,包括 Shenandoah ZGC
Java虚拟机2 G1垃圾回收详解, 参数, 日志
Java虚拟机3 Class文件及类加载
Java虚拟机4 方法调用原理、动态类型支持 (本文)
Java虚拟机5 编译与优化
Java虚拟机6 内存模型、线程、锁

总结 Java 不支持的语法特性
Java 协程:Loom Project 实战
其他JVM语言

栈帧

栈帧是方法执行的基本数据结构在这里插入图片描述
栈帧结构包括以下几部分:

  • 局部变量表(Local Variables Table)
    存放方法参数和方法内部定义的局部变量,长度在编译时确定

  • 操作栈(Operand Stack)
    用于算数运算、或调用其它方法时传递参数(下图8-2)和返回值

  • 动态连接
    指向运行时常量池[插图]中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接

  • 方法返回地址
    保存调用者的程序计数器,用于方法正常退出后恢复调用者的状态

  • 附加信息
    如调式、性能收集相关信息

在这里插入图片描述

方法调用

Java 虚拟机支持以下5条方法调用指令:

  • invokestatic 用于调用静态方法
  • invokespecial 用于调用实例构造器方法、私有方法和父类中的方法
  • invokevirtual 用于调用所有的虚方法
  • invokeinterface 用于调用接口方法,会在运行时再确定一个实现该接口的对象
  • invokedynamic 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法

解析(Resolution)调用:指调用非虚方法,包括静态方法、私有方法、构造器、父类方法、final 方法5中,会在类加载时将符号引用解析为直接引用。

分派(Dispatch)调用:分为静态分派、动态分派

  • 静态分派
    依赖静态类型来决定方法执行版本,用于方法重载(Overload)

  • 动态分派
    运行时根据动态类型决定方法执行版本,用于方法重写(Override)以及实现多态。

动态分派实现方式
为每个类在方法区建立一个虚方法表。虚方法表一般在类加载的连接阶段进行初始化。
在这里插入图片描述
除了虚方法表,虚拟机还使用类型继承关系分析(Class Hierarchy Analysis,CHA)、守护内联(Guarded Inlining)、内联缓存(Inline Cache)等方式来优化性能。

反射(reflect)调用

位于 java.lang.reflect 包下,getDeclaredMethod() 为获取类声明的方法。

import java.lang.reflect.Method;

public class Calculator {
    public static void main(String[] args) throws Throwable {
        Calculator calculator = new Calculator();
        // 后两个参数为 sum 方法参数类型
        Method method = Calculator.class.getDeclaredMethod("sum", int.class, int.class);
        int total = (int) method.invoke(calculator, 3, 5);
        System.out.println(total);  // 8
    }
    public int sum(int a, int b) {
        return a + b;
    }
}

invoke调用

位于 java.lang.invoke 包下,提供一种方法句柄(MethodHandle)的调用方式,类似于函数指针。
和反射的区别是,反射是模拟 Java 语言层面的方法调用,拥有方法的完整信息,包括方法名、注解等。而 MethodHandle 是模拟字节码层面的方法调用,仅包含执行方法的信息,且性能更高一些。

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class Calculator {
    public static void main(String[] args) throws Throwable {
        Calculator calculator = new Calculator();
        // 第一个参数为返回值类型,之后为参数类型
        MethodType mt = MethodType.methodType(int.class, int.class, int.class);
        // findVirtual 对应 invokevirtual 指令
        MethodHandle sum = MethodHandles.lookup().findVirtual(Calculator.class, "sum", mt).bindTo(calculator);
        int total = (int) sum.invokeExact(3, 5);
        System.out.println(total);  // 8
    }
    public int sum(int a, int b) {
        return a + b;
    }
}

动态类型语言

动态类型语言的类型检查在运行期而不是编译期,如 JavaScript、Python、PHP。
为了使Java虚拟机更好的支持动态类型语言,JDK 7 引入了 invokedynamic 指令。
在 Java 语言中,invokedynamic 用于实现 lambda 表达式,详见:

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

比如

List<Integer> list = ...
list.sort((a, b) -> a - b);

生成字节码为:

    INVOKEDYNAMIC compare()Ljava/util/Comparator; [
      // handle kind 0x6 : 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;
      // arguments:
      (Ljava/lang/Object;Ljava/lang/Object;)I, 
      // handle kind 0x6 : INVOKESTATIC
      Obj2.lambda$sort$0(Ljava/lang/Integer;Ljava/lang/Integer;)I, 
      (Ljava/lang/Integer;Ljava/lang/Integer;)I
    ]

invokedynamic 指令的第一个参数不再是方法符号引用,而是 CONSTANT_InvokeDynamic_info 常量,从这个新常量中可以得到3项信息:

  • 引导方法(Bootstrap Method),返回值是java.lang.invoke.CallSite对象,这个对象代表了真正要执行的目标方法调用
  • 方法类型(MethodType)
  • 名称

根据 CONSTANT_InvokeDynamic_info 常量中提供的信息,虚拟机可以找到并且执行引导方法,从而获得一个 CallSite 对象,最终调用到要执行的目标方法上。

字节码执行

执行Java代码一般有解释执行编译执行两种方式。
在这里插入图片描述
中间一行代表解释执行过程,下边一行代表编译执行过程。
Javac 编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。

指令集
Java字节码是基于栈的指令集架构(不完全是),而与之对应的是基于寄存器的指令集架构,二者区别如下:

  • 基于栈的指令集
iconst_1
iconst_1
iadd
istore_0

两条iconst_1指令连续把两个常量1压入栈后,iadd指令把栈顶的两个值出栈、相加,然后把结果放回栈顶,最后istore_0把栈顶的值放到局部变量表的第0个变量槽中。这种指令通常不带参数。

  • 基于寄存器的指令集
mov eax, 1
add eax, 1

mov指令把EAX寄存器的值设为1,然后add指令再把这个值加1,结果就保存在EAX寄存器里面。这种二地址指令是x86指令集中的主流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值