深入理解java虚拟机—虚拟机字节码执行引擎

引言

调用一个方法就会将该方法(将方法的各种信息封装成栈帧)进行压栈,方法结束就会出栈。而栈帧是支持方法调用和执行的基础结构,是虚拟机栈的元素,栈帧存储了一个方法的局部变量表,操作数栈,动态连接和方法返回地址信息等其他信息。在编译时就已经确定好了局部变量表的大小和操作数栈的大小。

局部变量表

局部变量表是用来存方法中的参数、变量,以slot为单位,this关键字是局部变量表的第一个参数(索引为0),这是编译时自动加上去的,this用于传递对象实例的引用。局部变量中的slot是可以复用的,如果pc指针超出了slot的作用域,那么这部分的slot则可以被其他变量使用。局部变量表也会影响垃圾回收,当然GC Roots一部分的局部变量仍然保持跟对象的关联,那么对象就不能被回收。

操作数栈

操作数栈也是一种先进后出的数据结构。操作数栈的最大深度在编译时就已经确定了,在代码运行时操作数栈深度不会超过最大值。操作数栈就是用来存放操作数的,32位的数据占操作数栈的容量为1,64位占容量为2。例如a+b,那么通过指令,会先将这两个数压栈,然后相加。在概念上操作数栈和局部变量表是分开的,但在虚拟机的实际实现中,有时会将两个栈帧的操作数栈和局部变量表共享内存。

动态连接

一部分方法的符号引用在运行期间转化为直接引用,这一部分被称为动态连接,栈帧中会保存方法区中的所属方法引用。

方法返回地址

一般方法返回地址保存的是调用者调用该方法时的pc程序计数器值。如果正常退出,该值作为返回地址,如果因为异常退出,那么返回地址根据异常处理器确定。方法的退出过程是将当前栈帧出栈,恢复上层方法的局部变量表和操作数栈,如果有返回值就将返回值压入调用者的栈中,调整pc计数器指向调用者方法后一条指令。

方法调用

方法的调用主要是确定被调用方法的版本。在类加载解析阶段,有一部分方法调用的符号引用转换成了直接引用,这些方法在程序开始之前就确定只有一个可调用的版本。这种方法调用叫做解析,主要包括静态方法,私有方法,实例构造方法和父类方法,这两种方法是不能被重写的,所以在编译时就被确定了,而且运行期不会被改变。这些方法都由invokestatic或invokespecial调用,都叫做非虚方法,除此之外还有一种是由final修饰的方法,虽然由invokevirtual调用,但是该方法无法被重写,所以也是非虚方法。总之解析调用在类加载时,方法的版本就被确定了,符号引用转换成了直接引用。
静态分派:
上面说的是只有一种方法版本,接下来说的是有多个方法版本,例如重载的方法,和重写的方法,这时候就要进行方法分派。方法分派有静态分派和动态分派。java是门面向对象的语言,多态是面向对象的其中一种特征。多态跟方法分派是密不可分的,方法分派就是虚拟机确定方法的调用版本,通俗就是确认调用哪个方法。静态分派是在编译期就确定了方法的调用版本,跟静态分派有关的就是方法重载。变量有静态类型,有实际类型。例如以下代码:Human是父类,Man是子类,那么Human是man这个变量的静态类型,Man是实际类型。

Human man = new Man();

如果一个相同的方法,参数类型不同就可以构成方法重载,那么静态分派是根据静态类型来确认方法版本。例如下面这段伪代码:
类中有两个方法,代码结果是调用第一个方法。

public void add(Human man){}
public void add(Man man){}
Human man = new Man();
方法调用:add(man);

动态分派:
动态分派跟方法重写有关,动态分派的方法选择是在运行时期确定,它会根据调用对象的实际类型去确认调用方法的版本。动态分派跟invokevirtual指令有关,该指令执行有以下步骤:
①找到栈顶第一个元素指向对象的实际类型,记作类型C;
②如果在类型C中找到与常量描述符和简单名称都相符的方法,并进行权限校验,如果通过则返回这个方法的直接引用,如果不通过则抛出访问非法异常。
③如果没找到,则按照继承关系从下往上依次对C的父类进行搜索。
④如果没找到,则抛出java.lang.AbstractMethodError异常。

单分派和多分派

方法的接受者和方法的参数统称为方法的宗量。单分派是基于一个宗量对方法进行选择,多分派是基于多个宗量对方法进行选择。java 是门静态多分派,动态单分派的语言,静态分派时根据方法的接收者和参数进行选择,动态分派根据方法接收者进行选择。

静态类型语言和动态类型语言

java是一门静态类型语言,静态类型语言和动态类型语言的区别在于:动态类型语言的类型检查是在运行期,而静态类型语言的检查是在编译期,动态类型语言只有赋了值才有了类型。

基于栈的字节码解释引擎

下面是大多数语言的编译过程,从第一行到第三行的过程是一种完全编译的编译过程,就像c语言这种通过编译最终形成可让机器执行的可执行代码;从第一行到第二行这种,就像java这种语言,实现为一个半独立的编译器。java语言的编译器将程序源码最终生成指令流,最后是由java虚拟机进行执行。
这里写图片描述
java的指令流是基于栈的指令集架构。还有一套常用的指令集架构就是基于寄存器的指令集。基于栈的指令集架构可移植性强;基于寄存器的架构跟处理器有关,可移植性不强,但是速度快。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值