当前位置: 首页 > article >正文

JAVA语言多态和动态语言实现原理

JAVA语言多态和动态语言实现原理

  • 前言
  • invoke指令
    • invokestatic
    • invokespecial
    • invokevirtual
    • invokeinteface
    • invokedynamic
    • Lambda
  • 总结

前言

我们编码java文件,javac编译class文件,java运行class,JVM执行main方法,加载链接初始化对应类,确定类/对象调用函数,执行对应函数方法code字节码,执行引擎不断取指执行。本文主要介绍虚拟机加载链接初始化类后,在执行函数调用时虚拟机如何通过相关invoke指令,确定具体执行函数,并以此介绍JAVA语言中多态和动态语言实现原理。

invoke指令

虚拟机再实现函数调涉及invoke指令,分为如下5类:

invoke指令函数调用
invokestatic执行类静态方法
invokespecial执行对象构造器,私有方法,父类方法
invokevirtual执行类对象的普通方法,(非静态,构造器,私有方法等),
虚拟机在运行期通过动态分派
invokeinterface接口对象执行接口方法,虚拟机在运行期通过动态分派
invokedynamic用户编码确定方法句柄

下面代码分别演示编译后函数调用使用invoke指令,使用jclasslib idea插件查看:

public class InvokeTestDemo extends AbsParent implements InterfaceParent {

    public static void main(String[] args) throws Throwable{
        InvokeTestDemo demo = new InvokeTestDemo();
        demo.test();
    }

    private void test() throws Throwable{
        // invokestatic 静态方法
        staticMethod();

        // invokespecial 构造方法
        new InvokeTestDemo();
        // invokespecial 私有方法
        privateMethod();
        // invokespecial 父类方法, 内部
        super.parentMethod();

        // invokevirtual
        finalMethod();
        publicMethod();
        parentMethod();
        interfaceMethod();

        // invokeinterface 接口方法
        InterfaceParent iTest = this;
        iTest.interfaceMethod();

        // invokedynamic
        Function<String, String> func = str -> "append " + str;
        func.apply("test");

        // MethodHandle
        Animal animal = new Animal();
        say(animal);

        People people = new People();
        say(people);
    }

    public static void say(Object obj) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType methodType = MethodType.methodType(void.class);
        MethodHandle methodHandle = lookup.findVirtual(obj.getClass(), "say", methodType);
        methodHandle.invoke(obj);
    }

    public static class Animal {
        public void say() {
            System.out.println("hello Animal");
        }
    }
    public static class People {
        public void say() {
            System.out.println("hello People");
        }
    }

    public static void staticMethod() {
    }

    private void privateMethod() {

    }

    public final void finalMethod() {

    }

    public void publicMethod() {

    }

    @Override
    public void interfaceMethod() {
    }
}

abstract class AbsParent {

    public void parentMethod() {

    }
}

interface InterfaceParent {

    void interfaceMethod();
}

invokestatic

调用类静态方法,属于静态分派,编译期可确定调用方法,运行期不变。
静态方法调用

invokespecial

调用对象构造器,私有方法,直接父类方法,属于静态分派,编译期可确定调用方法,运行期不变。
构造函数私有方法父类方法

invokevirtual

调用对象普通方法(非对象构造器,私有方法,直接父类方法),运行期根据对象具体类型,再确定具体执行方法,这个便是语言多态特征。
可重写方法

在具体实现上,以HotSpot为例,HotSpot使用了与C++虚函数类似的机制,同时为了避免每个对象都维护一个虚函数表,设计了Oop-Klass模型,用Klass类保存类的元数据和虚函数表vtable (virtual method table)。
vtable生成:
1、递归生成父类vtable
2、覆盖重写方法
3、追加新定义方法

Class Animal {
	
	public void breathe() {
	}
	
	public void sound() {
	}
	
}

Class Cat extends Animal{

	@override
	public void sound() {
	}
	
	public void run() {
	}
}

Animal vtable

方法方法地址
breathe@Animal#breathe
sound@Animal#sound

Cat vtable

方法方法地址备注
breathe@Animal#breathe取自父类
sound@Cat#sound本类重写
sound@Cat#run本类追加

vtable是用空间换取时间,类对应klass维护相对应的vtable,发生函数调用时,操作步骤如下:
1、通过函数操作数栈,获取栈顶调用函数者。
2、函数调用者查找实际类型class,进而确定klass关联vtable。
3、通过函数编号,找到vtable函数编号的方法代码code。
4、读取code字节码指令,执行引擎执行指令。

invokeinteface

以接口调用接口类方法,运行期根据接口实现类型,再确定具体执行方法。
接口方法

在具体实现上,以HotSpot为例,同vtable实现类似,接口是使用itable(inteface method table)实现方法动态分派。

itable存储格式如下:
itableOffsetEntry1
itableOffsetEntry2
……
itableOffsetEntryn
itableMethodEntry1
itableMethodEntry2
……
itableMethodEntryn

itable函数查找过程如下:
1、通过函数操作数栈,获取栈顶调用函数者。
2、函数调用者查找实际类型class,进而确定klass关联itable。
3、遍历itable查找接口entry对应itableOffsetEntry。
4、通过itableOffsetEntry

为什么需要itable,而不是用vtable去实现:
1、一个类继承是单继承,子类包含父类vtable,且和父类的函数编号是一致,可以直接使用父类的函数编号找到对应的子类实现函数。
2、一个类可以实现多个接口,而每个接口的函数编号是个接口相关,vtable无法解决多个对应接口的函数编号问题。
即:虚拟机规范规定,继承是单继承,实现是可实现多个接口。

invokedynamic

以上介绍invoke指令调用的函数流程实现是在虚拟机内部,编译器和虚拟机运行期完成调用方法查找并执行相应字节码,而invokedynamic指令则支持由用户编码确定方法句柄,具体查找过程如下:
1、JVM执行到invokedynamic指令时,它会首先查找与该指令关联的引导方法。
2、引导方法根据传入的参数动态生成和链接目标方法,并返回CallSite对象(封装了目标方法的所有信息,包括方法句柄、参数类型和返回类型)。
3、CallSite对象被创建并返回给invokedynamic指令,JVM就会将该指令与调用站点对象关联起来。
4、在后续的执行过程中,当再次遇到相同的invokedynamic指令时,JVM会直接通过调用站点对象调用目标方法,而无需再次执行引导方法。这种机制可以显著提高动态方法调用的性能。

某种意义上可以说invokedynamic指令与MethodHandle机制的作用是一样的。都是为了解决原有的4条”invoke”指令方法分派规则完全固化在虚拟机之中的问题。如何把查找目标方法的决定权从虚拟机转嫁到具体的用户代码中。

Lambda

lambda实现依赖invokedynamic执行,由编译器生成引导方法,生成调用点。

 invokedynamic #13 <apply, BootstrapMethods #0>

lambda引导方法
引导方法

过程如下:
1、Lambda 表达式解析常用引导方法,java.lang.invoke.LambdaMetafactory#metaFactory:
2、引导方法 metaMethod 根据这些参数生成 java.lang.invoke.CallSite 动态调用点
3、在引导方法中会动态生成一个模板匿名类
4、创建匿名类实例,执行方法调用。

总结

多态特性:虚拟机在运行时通过动态分派,查找确定要执行函数。
动态语言:虚拟机通过invokedynamic执行,由应用程序指定MethodHanle,确定函数调用者,方法,参数,调用方法等,实现动态语言。

参考: https://blog.csdn.net/weixin_47184173/article/details/109903542


http://www.kler.cn/a/378440.html

相关文章:

  • npm入门教程5:package.json
  • 流畅!HTMLCSS打造网格方块加载动画
  • SpringBoot+VUE2完成WebSocket聊天(数据入库)
  • Manjaro Linux安装过程简介
  • 网络编程入门
  • 【pycharm jupyter】启动报错
  • 深度学习:反向传播算法简介
  • 一体化运维监控管理平台详解:构建高效运维体系
  • 如何通过OpenAI Gym学习强化学习
  • 乡村景区一体化系统(门票,餐饮,便利店,果园,娱乐,停车收费
  • 两个壁面之间夹一个圆柱形杆的温度分布
  • LeetCode 684.冗余连接:拓扑排序+哈希表(O(n)) 或 并查集(O(nlog n)-O(nα(n)))
  • 使用GetX实现GetPage中间件
  • WordPress在windows下安装
  • 【Git】从 GitHub 仓库中移除误提交的 IntelliJ IDEA 配置文件夹 .idea 并将其添加到 .gitignore 文件中
  • MyBatis-Plus快速入门:从安装到第一个Demo
  • React Native 0.76 重大更新:新架构全面启用
  • 基于Python的自然语言处理系列(47):DistilBERT:更小、更快、更省、更轻的BERT版本
  • C++编程法则365天一天一条(344)理解std::optional的设计初衷
  • 数据库日志分析 ApexSQLLog
  • 基于SSM+VUE历史车轮网站JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解
  • Zypher Network:全栈式 Web3 游戏引擎,服务器抽象叙事的领导者
  • 2.若依vue表格数据根据不同状态显示不同颜色style
  • 【Flask框架】10、Flask项目拆分规范
  • 论文略读:Self-Knowledge Guided Retrieval Augmentation for Large Language Models
  • SQL之排名窗口函数RANK()、ROW_NUMBER()、DENSE_RANK() 和 NTILE() 的区别(SQL 和 Hive SQL 都支持)