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. Unity 屏幕外死亡的敌人的分数显示在屏幕内

    在敌人死亡后,会出现分数,如果敌人死亡的位置在屏幕内,那么使得获得的分数显示在屏幕内,超出屏幕范围的,显示在屏幕外 当然,这里例子是使得场景中的物体显示在屏幕内,当然也可以使用纯粹的UGUI物体的显示 ...

  2. Fiddler工具使用介绍二

    在上一篇中介绍了Fiddler的基本使用方法.通过上一篇的操作我们可以直接抓取浏览器的数据包.但在APP测试中,我们需要抓取手机APP上的数据包,应该怎么操作呢? Andriod配置方法: 1)确保手 ...

  3. docker部署PiggyMetrics分布式微服务

    在上一篇文章里http://www.cnblogs.com/lyhero11/p/8686058.html, 讲解了如何在windows10下安装docker社区版. 那如何利用docker落地一个分 ...

  4. VS2017新建视图中文乱码解决办法

    问题:VS2017 ASP.NET Core 新建视频默认为ASNI编码格式 解决办法 1:直接将视图页面通过记事本打开,然后另存为UTF-8解决. 2:安装扩展TextTools解决视图文件编码问题 ...

  5. jQuery的ajax的post请求json格式无法上传空数组

    问题描述:在和后端对接时,使用jquery的ajax的post方式向后端传递一序列约定好格式的对象数组.遇到了一个现象:如果对象中的数组是空数组,那么在请求参数中是不会出现的. 以下是数据的对比:   ...

  6. css经典布局—Sticky footers布局

    参考:http://www.w3cplus.com/CSS3/css-secrets/sticky-footers.html 效果:将footer固定到底部.文章内容不足满屏时 footer在底部,超 ...

  7. CSS中text-shadow的几个好看的文字demo及其代码

    最近有看到一些镂空的或者立体的文字设计图非常好看,然后想了想,应该是使用text-shadow来实现的,这里我贴出我仿的八个demo,分享给大家 首先是HTML代码 <div class=&qu ...

  8. python读取文件首行和最后一行

    python读取文件最后一行两种方式 1)常规方法:从前往后依次读取 步骤:open打开文件. 读取文件,把文件所有行读入内存. 遍历所有行,提取指定行的数据. 优点:简单,方便 缺点:当文件大了以后 ...

  9. linux(乌班图)下执行pip没有问题,执行sudo pip报错的问题

    最近刚装好linux的虚拟机,在装一个套件时提示权限不足,于是添加上了 sudo 命令,结果直接报以下错误, Traceback (most recent call last): File " ...

  10. POJ 2453

    #include <iostream> #include <algorithm> #include <cmath> #define MAXN 1000 #defin ...