前言

本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍:

我的 GIthub 博客

学习导图

一.为什么要学习字节码执行引擎?

代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步

首先,抛出灵魂三问:

  • 虚拟机在执行代码的时候,如何找到正确的方法呢?
  • 如何执行方法内的字节码呢?
  • 执行代码时涉及的内存结构有哪些呢?

如果你对上述问题理解得还不是特别透彻的话,可以看下这篇文章;如果理解了,你可以关闭网页,打开游戏放松了hhh

下面,笔者将带你探究 JVM 核心的组成部分之一——执行引擎。

二.核心知识点归纳

2.1 概述

Q1:虚拟机与物理机的异同

  • 相同点:都有代码执行能力
  • 不同点:
  • 物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的
  • 虚拟机的执行引擎是由自定义的,可自行制定指令集与执行引擎的结构体系,且能够执行不被硬件直接支持的指令集格式

Q2:有关 JVM 字节码执行引擎的概念模型

  • 外观上:所有 JVM 的执行引擎都是一致的。输入的是字节码文件,处理的是字节码解析的等效过程,输出的是执行结果

  • 从实现上,执行引擎有多种执行 Java 代码的选择
  • 解释执行:通过解释器执行
  • 编译执行:通过即时编译器产生本地代码执行
  • 两者兼备,甚至还会包含几个不同级别的编译器执行引擎

2.2 运行时栈帧结构

2.2.1 基本概念

笔者之前在 一文洞悉 JVM 内存管理机制 中就谈到过虚拟机栈,相信看过的读者都有印象

  • 栈帧:用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的栈元素
  • 存储内容:方法的局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息
  • 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程
  • 一个栈帧需要分配多少内存在程序编译期就已确定,而不会受到程序运行期变量数据的影响
  • 对于执行引擎来说,只有位于栈顶的栈帧(当前栈帧)才是有效的,即所有字节码指令只对当前栈帧进行操作,与当前栈帧相关联的方法称为当前方法

2.2.2 局部变量表

  • 定义:局部变量表是一组变量值存储空间
  • 作用:存放方法参数和方法内部定义的局部变量
  • 分配时期:Java 程序编译Class 文件时,会在方法的 Code 属性的 max_locals 数据项中确定了该方法所需要分配的局部变量表的最大容量
  • 最小单位:变量槽
  • 大小:虚拟机规范中没有明确指明一个变量槽占用的内存空间大小,允许变量槽长度随着处理器、操作系统或虚拟机的不同而发生变化

    1. 对于 32 位以内的数据类型(booleanbytecharshortintfloatreferencereturnAddress ),虚拟机会为其分配一个变量槽空间
    2. 对于 64 位的数据类型(longdouble ),虚拟机会以高位对齐的方式为其分配两个连续的变量槽空间
  • 特点:可重用。为了尽可能节省栈帧空间,若当前字节码 PC 计数器的值已超出了某个变量的作用域,则该变量对应的变量槽可交给其他变量使用
  • 访问方式:通过索引定位。索引值的范围是从 0 开始至局部变量表最大的变量槽数量

  • 局部变量表第一项是名为 this 的一个当前类引用,它指向堆中当前对象的引用(由反编译得到的局部变量表可知)

2.2.3 操作数栈

  • 操作数栈是一个后入先出栈

  • 作用:在方法执行过程中,写入(进栈)和提取(出栈)各种字节码指令

  • 分配时期:同上,在编译时会在方法的 Code 属性的 max_stacks 数据项中确定操作数栈的最大深度

  • 栈容量:操作数栈的每一个元素可以是任意的 Java 数据类型 ——32 位数据类型所占的栈容量为 164 位数据类型所占的栈容量为 2

  • 注意:操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译时编译器需要验证一次、在类校验阶段的数据流分析中还要再次验证

2.2.4 动态连接

  • 定义:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接
  • 静态解析和动态连接区别:

Class 文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数,这些符号引用:

  • 一部分会在类加载阶段或者第一次使用的时候就转化为直接引用(静态解析
  • 另一部分会在每一次运行期间转化为直接引用(动态连接

2.2.5 方法返回地址

  • 方法退出的两种方式:
  • 正常退出:执行中遇到任意一个方法返回的字节码指令
  • 异常退出:执行中遇到异常、且在本方法的异常表中没有搜索到匹配的异常处理器区处理
  • 作用:在方法返回时都可能在栈帧中保存一些信息,用于恢复上层方法调用者的执行状态
  • 正常退出时,调用者的 PC 计数器的值可以作为返回地址
  • 异常退出时,通过异常处理器表来确定返回地址
  • 方法退出的执行操作:
  • 恢复上层方法的局部变量表和操作数栈
  • 若有返回值把它压入调用者栈帧的操作数栈中
  • 调整 PC 计数器的值以指向方法调用指令后面的一条指令等

在实际开发中,一般会把动态连接、方法返回地址与其他附加信息全部一起称为栈帧信息

2.3 方法调用

  • 方法调用是最普遍且频繁的操作
  • 任务:确定被调用方法的版本,即调用哪一个方法,不涉及方法内部的具体运行过程

下面笔者将为大家详细讲解方法调用的类型

2.3.1 解析调用

笔者之前在 一夜搞懂 | JVM 类加载机制中就谈到过解析,感觉有点混淆的,可以回去看下

  • 特点:

    1. 是静态过程
    2. 在编译期间就完全确定,在类装载解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,而不会延迟到运行期再去完成,即编译期可知、运行期不变
  • 适用对象:private 修饰的私有方法,类静态方法,类实例构造器父类方法

2.3.2 分派调用

Q1:什么是静态类型?什么是实际类型?

A1:这个用代码来说比较简便, Talk is cheap ! Show me the code !

//父类
public class Human {
}
//子类
public class Man extends Human {
}
public class Main {

    public static void main(String[] args) {
//这里的 Human 是静态类型,Man 是实际类型
Human man=new Man();
} }

1.静态分派

  • 依赖静态类型来定位方法的执行版本
  • 典型应用是方法重载
  • 发生在编译阶段,不由 JVM 来执行

单纯说未免有些许抽象,所以特地用下面的 DEMO 来帮助了解

public class Father {
}
public class Son extends Father {
}
public class Daughter extends Father {
}
public class Hello {
public void sayHello(Father father){
System.out.println("hello , i am the father");
}
public void sayHello(Daughter daughter){
System.out.println("hello i am the daughter");
}
public void sayHello(Son son){
System.out.println("hello i am the son");
}
}
public static void main(String[] args){
Father son = new Son();
Father daughter = new Daughter();
Hello hello = new Hello();
hello.sayHello(son);
hello.sayHello(daughter);
}

输出结果如下:

hello , i am the father

hello , i am the father

我们的编译器在生成字节码指令的时候会根据变量的静态类型选择调用合适的方法。就我们上述的例子而言:

2.动态分派

  • 依赖动态类型来定位方法的执行版本

  • 典型应用是方法重写

  • 发生在运行阶段,由 JVM 来执行

    单纯说未免有些许抽象,所以特地用下面的 DEMO 来帮助了解

public class Father {
public void sayHello(){
System.out.println("hello world ---- father");
}
} //继承 + 方法重写
public class Son extends Father {
@Override
public void sayHello(){
System.out.println("hello world ---- son");
}
}
public static void main(String[] args){
Father son = new Son();
son.sayHello();
}

输出结果如下:

hello world ---- son

我们接着来看一下字节码指令调用情况

疑惑来了,我们可以看到,JVM 选择调用的是静态类型的对应方法,但是为什么最终的结果却调用了是实际类型的对应方法呢?

当我们将要调用某个类型实例的具体方法时,会首先将当前实例压入操作数栈,然后我们的 invokevirtual 指令需要完成以下几个步骤才能实现对一个方法的调用:

因此,疑惑自然解决了

3.单分派

  • 含义:根据一个宗量对目标方法进行选择(方法的接受者与方法的参数统称为方法的宗量)

4.多分派

  • 含义:根据多于一个宗量对目标方法进行选择

想了解 静态多分派,动态单分派 的可以看下这篇文章:Java 中的静态单多分派与动态单分派

三.碎碎念

恭喜你!已经看完了前面的文章,相信你对JVM字节码执行引擎已经有一定深度的了解!你可以稍微放松奖励自己一下,可以睡一个美美的觉,明天起来继续冲冲冲!!!


如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力

本文参考链接:

一夜搞懂 | JVM 字节码执行引擎的更多相关文章

  1. JVM总结(五):JVM字节码执行引擎

    JVM字节码执行引擎 运行时栈帧结构 局部变量表 操作数栈 动态连接 方法返回地址 附加信息 方法调用 解析 分派 –“重载”和“重写”的实现 静态分派 动态分派 单分派和多分派 JVM动态分派的实现 ...

  2. 深入理解JVM—字节码执行引擎

    原文地址:http://yhjhappy234.blog.163.com/blog/static/3163283220122204355694/ 前面我们不止一次的提到,Java是一种跨平台的语言,为 ...

  3. JVM字节码执行引擎和动态绑定原理

    1.执行引擎 所有Java虚拟机的执行引擎都是一致的: 输入的是字节码文件,处理过程就是解析过程,最后输出执行结果. 在整个过程不同的数据在不同的结构中进行处理. 2.栈帧 jvm进行方法调用和方法执 ...

  4. JVM字节码执行引擎

    一.概述 在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译器执行(通过即时编译器产生本地代码执行)两种选择,所有的Java虚拟机的执行引擎都是一致的:输 ...

  5. 图解JVM字节码执行引擎之栈帧结构

    一.执行引擎      “虚拟机”的概念是相对于“物理机”而言的,这两种“机器”都有执行代码的能力.物理机的执行引擎是直接建立在硬件处理器.物理寄存器.指令集和操作系统层面的:而“虚拟机”的执行引擎是 ...

  6. 深入理解JVM虚拟机5:虚拟机字节码执行引擎

    虚拟机字节码执行引擎   转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...

  7. JVM学习笔记:字节码执行引擎

    JVM学习笔记:字节码执行引擎 移步大神贴:http://rednaxelafx.iteye.com/blog/492667  

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

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

  9. JVM基础结构与字节码执行引擎

    JVM基础结构 JVM内部结构如下:栈.堆. 栈 JVM中的栈主要是指线程里面的栈,里面有方法栈.native方法栈.PC寄存器等等:每个方法栈是由栈帧组成的:每个栈帧是由局部变量表.操作数栈等组成. ...

随机推荐

  1. docker 学习(四)

    1.Dockerfile简介 1)什么是Dockerfile Dockerfile是一个包含用于组合映像的命令的文本文档.可以使用在命令行中调用任何命令. Docker通过读取Dockerfile中的 ...

  2. PAT-字符串处理-B1006 换个格式输出整数 (15分)

    题目描述: 让我们用字母 B 来表示“百”.字母 S 表示“十”,用 12...n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 3 位的正整数.例如 234 应该被输出为 ...

  3. Access Token 机制详解

    我们在访问很多大公司的开放 api 的时候,都会发现这些 api 要求传递一个 access token 参数.这个参数是什么呢?需要去哪里获取这个 access token 呢? access to ...

  4. 单页面和多页面的网页差别比较(SPA)

      单页面应用(singlePAge Web Application) 多页面应用MultiPage Applicaton,MPA) 组成 一个外壳页面和多个页面片段组成 多个完整的页面组成 资源公用 ...

  5. python 深浅拷贝 元组 字典 集合操作

    深浅拷贝 :值拷贝 :ls = [,,] res = ls 则print(res)就是[,,] 浅拷贝 :ls.copy() 深拷贝:ls3 = deepcopy(ls) # 新开辟列表空间,ls列表 ...

  6. 【读后感】《Java编程思想》~ 异常

    [读后感]<Java编程思想>~异常 终于拿出压箱底的那本<Java编程思想>.这本书我年轻的时候就买了,但是翻过几页后就放弃了.没想到这两天翻了一下,真的有收获. 看了一下第 ...

  7. 盘点Linux运维常用工具(一)-web篇之httpd

    #前言:想把自己学的各种服务进行分类归档起来,于是就写了盘点Linux运维常用工具,Linux方面使用到的web应用服务有httpd(apache).nginx.tomcat.lighttpd,先了解 ...

  8. Python中使用subprocess模块远程执行命令

    使用subprocess模块执行远程命令 服务端代码 1 import socket 2 import subprocess 3 4 sh_server = socket.socket() 5 sh_ ...

  9. Yaml文件,超详细讲解

    YAML文件简单介绍 YAML 是一种可读性非常高,与程序语言数据结构非常接近.同时具备丰富的表达能力和可扩展性,并且易于使用的数据标记语言. YAML全称其实是"YAML Ain't a ...

  10. juery 实现选项卡

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...