JVM系列-字节码执行

这篇文章对应于java虚拟机的第8章,在学习虚拟机的时候,读完类加载和字节码执行这几章节,感觉自己没有理解透彻,整体流程也没有梳理清楚。最后又读了一遍,才把思路梳理清楚。看书还是要站在一定的高度去看,一开始看的时候,只是学到了一个个的知识点,单独看一个知识点倒是知道什么意思,但是看完之后,依然不清楚整体的流程。

这篇文章需要分成三部分来说,第一部分是背景知识,第二部分是方法调用,第三部分是方法执行。
背景知识是说一下方法调用和方法执行依赖的数据结构,也就是栈帧。
方法调用是确定被调用方法的版本,也就是到底要调用哪个方法,不涉及方法内部的具体运行过程。
方法执行是虚拟机执行方法中的字节码指令的过程,也就是解释执行和编译执行。

所以,通俗的讲,这篇文章的内容,就是去说明虚拟机是如何选择要调用哪个方法,以及如何执行这些方法的。不过在这篇文章中,偏向于对整个流程进行一个梳理和总结,细节上涉及的可能会比较少。

一、背景知识:
栈帧是支持虚拟机进行方法调用和方法执行的数据结构,在JVM内存中,是虚拟机栈的栈元素,虚拟机栈是线程私有的。
一个方法从开始调用到执行完成,就对应这一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。

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

在编译过程中,虚拟机并不知道目标方法的具体内存地址。因此,Java 编译器会暂时用符号引用来表示该目标方法。这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符。

二、方法调用:
这里说的方法调用,其实就是把class文件的符号引用转换为直接引用的过程,也就是确定调用哪个方法。
在编译后的class文件中,对调用的方法都是存储的符号引用,而不是方法实际的内存地址。需要在类加载期间,甚至是运行期间,才能确定目标方法的直接引用。
在类加载阶段,会把一部分符号引用转换为直接引用,转换的前提是方法在真正运行之前就有一个可确定的调用版本,并且这个版本在运行期是不可变的。在Java中,符合这种要求的方法名主要就是静态方法和私有方法,静态方法直接与类关联,私有方法在外部不可以被访问,也不可以被继承,这两种方法都不会存在其他版本,可以在类加载阶段进行解析。被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一调用版本,所以,除了静态方法和私有方法之外,实例构造器和父类方法也可以在类加载阶段把符号引用解析为直接引用。
而对于那些虚方法(final的除外),比如那些公有的实例方法,或者接口方法,这种方法调用是通过invokevirtual和invokeinterface指令调用的。对invokevirtual指令来说,调用需要先根据栈帧中的操作数栈存放的对象的实际类型来查找方法,所以这种就是在运行期间才能确定方法的直接引用。
所以,虚拟机把符号引用替换为直接引用的过程,在类加载阶段和运行阶段都会发生。
那接下来还有个问题,虚拟机是怎么区分方法的呢?
虚拟机识别方法的关键在于类名、方法名以及方法描述符(method descriptor)。方法描述符是由方法的参数类型以及返回类型所构成。这里就和Java编译器不一样了,因为虚拟机会把方法的返回类型作为判断条件,方法名相同,入参相同但返回值不同的方法,对虚拟机来说就是两个方法。但是这种方法在Java编译器就会报错了。

说完了这些,再聊下我们常见的重载和重写,这种方法是如何进行选择和调用的。
先说个定义,对于 Animal a = new Dog(); 这句代码来说,a是变量,Animal是这个变量的静态类型,Dog是这个变量的实际类型。
对于方法重载,在编译阶段,Java编译器就会根据参数的静态类型决定使用哪个重载的版本。所以,准确的来说,这个选择和虚拟机并没有关系。在把代码编译为.class文件的时候,就已经选择好了。
对于方法重写,则是运行时根据对象的实际类型来查找对应的方法,所以是在运行期间才把符号引用替换为直接引用的。

三、方法执行:
获取到字节码指令后,虚拟机就需要执行这些指令了。从硬件视角来看,Java字节码无法直接执行,需要Java虚拟机将字节码翻译为机器码才行。而虚拟机把字节码翻译为机器码有两种方式:解释执行和编译执行。

解释执行:通过解释器,逐条将字节码翻译成机器码。
编译执行:通过即时编译器,将一个方法中包含的所有字节码编译成机器码,并进行个各种层次的优化。

解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释执行节约内存,反之可以使用编译执行来提升效率

HotSpot 默认采用混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值