小目标之读懂JVM—虚拟机字节码执行引擎方法调用

17 篇文章 0 订阅
13 篇文章 0 订阅

方法调用不等同于方法执行,方法调用阶段的唯一任务就是确定被调用方法的版本。方法调用是程序运行时最普遍、最频繁的操作。Class文件中存储的都是方法调用的符号引用,只有在类加载期间或者到运行期间才能确定目标方法的直接引用。

Java虚拟机提供了5条方法调用字节码指令。invokestatic:调用静态方法。invokespecial:调用实例构造器<init>方法、私有方法和父类方法。invokevirtual:调用所有的虚方法,虚方法是指存在多个版本的方法,例如重载和重写的方法。invokeinterface:调用接口方法,会在运行时在确定一个实现此接口的对象。invokedynamic:运行时动态解析方法调用,调用的方法版本由用户设定的引导方法决定。

在类加载的解析阶段,方法调用的一部分符号引用会转化为直接引用。Java语言中的静态方法和私有方法两大类在类加载阶段进行解析,因为它们不可能通过继承或别的方式重写其他版本。 Java中的非虚方法除了使用invokestatic、invokespecial调用的方法之外还有一种就是final修饰的方法,final方法是通过invokevirtual指令调用,但是由于它无法被覆盖,无其他版本,所以并不用进行多态选择,Java语言规范也明确说明final方法是一种非虚方法。

虚方法的调用涉及到分派的概念,Java有3个基本特征,封装、继承和多态,重载和重写是多态的一种基本体现。重载和重写又分别体现了静态分派和动态分派的概念。Human man=new Man()这行代码中的Human声明称为变量的静态类型(Static Type),而Man则称为变量的实际类型(Actual Type)。编译阶段编译器会根据参数的静态类型来决定使用哪个重载版本,而运行时会在运行期确定方法调用接受者(即对应实例对象)的实际类型,根据实际类型确定方法执行版本。

分派可根据宗量多少划分为单分派和多分派。宗量的定义即为方法的接受者和方法的参数。编译阶段编译时静态分派会根据重载的方法接受者和参数的数量产生多种指令。而运行时动态分派只会根据方法的接受者确定调用方法指令。所以今天的Java语言属于静态多分派、动态单分派的语言。

动态分配是非常频繁的动作,所以虚拟机实际实现中基于性能的考虑会采取一些优化手段。最常用的"稳定优化"手段就是为类在方法区中建立一个虚方法表(Vritual Method Table),使用虚方法表索引来代替元数据查找以提高性能。方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。

invokedynamic是JDK1.7为了实现动态类型语言支持而做出的改进,也是JDK1.8Lambda表达式的技术准备。动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期。例如obj.println("hello world"),javascript可编译通过,运行时判断obj是否又println方法属性,但是java要求obj声明必须是一个包含println方法属性的实例,例如obj是一个带有pringln方法的实例,但是用Object去声明obj,则编译时会抛出异常而无法正常编译。动态类型语言的一个特征是“变量无类型而变量值才有类型”,有利于代码的清晰和简洁。静态类型语言可以帮助编译器确定严谨的类型检查,编码时就能避免出现与类型相关的问题。invokedunamic的分派逻辑不由虚拟机决定,而是由程序员决定的。可以帮助程序员掌控方法分派。

Java编译器的指令流基本上是一种基于栈的指令集架构,还有一种是基于寄存器的指令集架构。基于栈的指令集的主要优点是可移植、程序不受硬件约束、编译器实现更为简单。基于寄存器的指令集的有点在于速度更快、完成相同功能所需指令数量一般更少、内存访问也比栈要少。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值