Java虚拟机--虚拟机字节码执行引擎

所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。

运行时栈帧结构

用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的栈元素。每一个方法从调用开始到执行完成的过程,都对应一个栈帧在虚拟机栈中的入栈出栈过程

由于虚拟机栈是线程私有的,所以每一个线程都有一个自己的虚拟机栈,而每个虚拟机栈都是由许多栈帧组成。每一个栈帧都包括

  • 局部变量表
  • 操作数栈
  • 动态连接
  • 方法返回地址
  • 额外附加信息

处于栈顶的称为当前栈帧,对于执行引擎,在活动线程中只有当前栈帧是有效的,与当前栈帧关联的方法称为当前方法

局部变量表

用于存放方法参数和方法内定义的局部变量。虚拟机通过索引定位的方式使用局部变量表,局部变量表的容量以变量槽(Variable Slot)为最小单位。局部变量不像类变量那样有“准备阶段”,不会被赋予系统初始值,所以在定义局部变量时一定要对其赋值。

操作数栈

又被称为操作栈,当一个方法开始执行时,操作栈是空的,随着方法的执行,各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈的操作。如在进行算术运算时就是通过操作数栈来进行的。

动态连接

每个栈帧都包括一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时就转换为直接引用,这种转化称静态解析;另外一部分在每一次运行期间转化为直接引用,称为动态连接

方法返回地址

方法开始执行后,只有两种方法可退出该方法:

  • 正常完成出口:执行引擎遇到任意一个方法返回的字节码指令;
  • 异常完成出口:执行过程中遇到异常,在本方法中没有搜索到匹配的异常处理器而导致的退出。

方法退出的过程实际上等同于将当前栈帧出栈,退出时可能执行的操作有:恢复上层方法的局部变量表和操作舒展,如有返回值,把返回值压入调用者栈帧的操作数栈中,调用PC计数值以指向方法调用指令后面的一条指令。

方法调用

方法调用不同于方法执行,方法调用只是确定要调用哪一个方法,还不涉及方法内部的具体运行过程。所有方法调用在Class文件中都是一个常量池的符号引用,在类加载甚至是运行期间才能确定目标方法的直接引用。在类加载的解析阶段,会将一部分的符号引用转化成直接引用(静态解析),这种解析能成立的前提:

  • 方法在程序真正运行之前就有一个可确定的调用版本
  • 且这个方法在运行期间不可改变

满足上述条件的方法主要有两大类

  • 静态方法,直接与类型关联
  • 私有方法,在外部不能被访问

这两种方法各自的特点决定了它们不能通过继承或者别的方式重写其他版本,因此它们适合在类加载阶段进行解析。

Java虚拟机提供了5条方法调用字节码指令

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

只要能被invokestatic、invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器父类方法,它们可以在类加载时就把符号引用解析为该方法的直接引用。这些方法称为非虚方法,除开final方法外的其他方法都称为虚方法

分派

分派调用可能是静态的也可能是动态的。

静态分派

静态分派:所有以来静态类型来定位方法执行版本的分派动作称为静态分配。静态分配和重载的关系密切。

什么是静态类型,举个例子,比如类Human、Man和Woman,其中Man和Woman继承了Human。

package exercise;

public class StaticDispatch {

    static class Human {}
    static class Man extends Human{}
    static class Woman extends Human{}

    public void someMethod(Human human) {
        System.out.println("Human");
    }

    public void someMethod(Man man) {
        System.out.println("Man");
    }

    public void someMethod(Woman woman) {
        System.out.println("Woman");
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch s = new StaticDispatch();
        s.someMethod(man);
        s.someMethod(woman);
    }
}

上述main方法中,称Human为静态类型,或者外观类型,而Man或者Woman被称为实际类型

静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型在编译期时可知的;实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。

// 实际类型变化
Human man = new Man();
man = new Woman();

// 静态类型变化
s.someMethod((Man) man);
s.someMethod((Woman) man);

编译器在重载时是通过参数的静态的静态类型而不是实际类型作为判断依据的。因此上面的例子中会打印两个"Human"而不是一个打印"Man"一个打印"Woman"。

如果在main中改为

s.someMethod((Man)man);
s.someMethod((Woman) woman);

将会分别打印"Man"和"Woman"。

动态分派

动态分配:在运行期间根据实际类型确定方法的执行版本的分配过程。动态分配和多态中的重写(Override)有密切的关联。

举个例子

package exercise;

public class DynamicDispatch {
    static abstract class Human {
        protected abstract void someMethod();
    }
    static class Man extends Human {

        @Override
        protected void someMethod() {
            System.out.println("Man");
        }
    }

    static class Woman extends Human {

        @Override
        protected void someMethod() {
            System.out.println("Woman");
        }
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        man.someMethod();
        woman.someMethod();
        man = new Woman();
        man.someMethod();
    }
}

上面的例子会打印

Man
Woman
Woman

单分派和多分派

方法的接收者和方法参数统称为方法的宗量,根据分派基于多少种宗量,可以将分派分配划分为多分派单分派

如果在分派过程中既要依据方法接收者而要依据方法参数,就是多分派,Java的静态分派属于多分派;如果在分派过程中只有某一种宗量作为选择依据,其他宗量不会影响对虚拟机的选择,比如方法参数不影响虚拟机选择,唯一可以影响虚拟机选择的因素只有此方法的接收者,则是单分派,Java的动态分派属于单分派

总结一下:目前Java是一门静态多分派、动态单分派的语言。


by @sunhaiyu

2018.6.16

Java虚拟机--虚拟机字节码执行引擎的更多相关文章

  1. 深入理解Java虚拟机(字节码执行引擎)

    深入理解Java虚拟机(字节码执行引擎) 本文首发于微信公众号:BaronTalk 执行引擎是 Java 虚拟机最核心的组成部分之一.「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力, ...

  2. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  3. 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性

    我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...

  4. 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的

    概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...

  5. 深入理解Java虚拟机读书笔记5----虚拟机字节码执行引擎

    五 虚拟机字节码执行引擎   1 运行时栈帧结构     ---栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素.     ---栈帧中存储了方法的局部变 ...

  6. 深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

    目录 1.类文件结构 1.1 Class类文件结构 1.2 魔数与Class文件的版本 1.3 常量池 1.4 访问标志 1.5 类索引.父索引与接口索引集合 1.6 字段表集合 1.7 方法集合 1 ...

  7. java虚拟机字节码执行引擎

    定义 java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果.在不同的虚拟机实现里,执行引擎在执行java代码 ...

  8. Java虚拟机-字节码执行引擎

    概述 Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade).不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行. 运行时帧栈结构 栈帧(Sta ...

  9. JAVA虚拟机:虚拟机字节码执行引擎

    “虚拟机”是一个相对“物理机”的概念,这两种机器都有代码执行能力. 物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的. 虚拟机的执行引擎由自己实现,自行制定指令集与执行引擎的结构体系 ...

随机推荐

  1. .NetCore 发布到 Centos docker

    [搭建环境] 系统:CentOS 7.0    容器:Docker 1.13.1    .Net Core 版本:.NET Core 2.2     工具:putty.filezilla 等. 一.安 ...

  2. .net core 使用 AspectCore 实现简易的AopCache。

    (第一次写博客,好紧张!!!) 源码地址:传送门 项目中有很多缓存的需求,能自己定义缓存key和时间,能根据key去清理缓存. 网上找了一圈,有很多基于aop的缓存组件,但是都不满足我的需求.故造了个 ...

  3. ASP.NET MVC的切片(Section)脚本(script)

    在ASP.NET MVC使用切片脚本,实在是很溜. 在使用layout之后,只是视图套用_Layout之后,在视图中任一位置任一时候均可以使用切片脚本. 首先在_Layout.cshtml定一些规则: ...

  4. 利用App漏洞获利2800多万元,企业该如何避免类似事件?

    上个月,上海警方抓捕了一个利用网上银行漏洞非法获利的犯罪团伙,该团伙利用银行App漏洞非法获利2800多万元. 据悉,该团伙使用技术软件成倍放大定期存单金额,从而非法获利.理财邦的一篇文章分析了犯罪嫌 ...

  5. java多线程面试题整理及答案(2018年)

    1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速.比如,如果一个线程完 ...

  6. Linux系统磁盘与分区管理(7)

    Linux最传统的磁盘文件系统(filesystem)使用的是EXT4格式,所以要了解文件系统就得要由认识EXT4开始,而文件系统是创建在硬盘上面的,因此我们得了解硬盘的物理组成才行,下面我们回来详细 ...

  7. [Ynoi2016]这是我自己的发明(莫队)

    话说这道题数据是不是都是链啊,我不手动扩栈就全 \(RE\)... 不过 \(A\) 了这题还是很爽的,通过昨晚到今天早上的奋斗,终于肝出了这题 其实楼上说的也差不多了,就是把区间拆掉然后莫队瞎搞 弱 ...

  8. c setjmp longjmp

    http://coolshell.cn/?s=setjmp http://www.cnblogs.com/hazir/p/c_setjmp_longjmp.html double divide(dou ...

  9. Spring Boot中使用@Async实现异步调用

    在Spring Boot中,我们只需要通过使用@Async注解就能简单的将原来的同步函数变为异步函数,为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsyn ...

  10. 在centos使用redis几个坑

    问题来源 最近公司的平台需要做一些分布式的规划,其中会话我们打算用redis来存储,因为之前也有了解过redis,但都是在windows上使用,为了发挥redis的优势,这次我们打算直接在Linux上 ...