一.前言

物理机的执行引擎是直接在物理硬件如CPU、操作系统、指令集上运行的,但是对于虚拟机来讲,他的执行引擎由自己实现。 执行引擎有统一的外观(Java虚拟机规范),不同类型的虚拟机都遵循了这一规范,输入字节码文件,解析字节码处理,然后输出结果。

二.运行时栈帧结构

1、栈帧概念
栈帧(Stack Frame)用于支持方法调用和执行的数据结构,包含了局部变量表、操作数栈、动态连接和方法返回地址。

  • 局部变量表大小(max_locals),栈帧深度在编译时已经确定,并写入到了Code属性中;
  • 执行引擎运行的所有字节码指令都只针对当前栈进行操作;

2、局部变量表
局部变量表存储了方法参数以及方法内定义的局部变量。

  • Slot(变量槽):局部变量表容量最小单位,可以存放32位以内的数据类型;
  • refrence:
    • 直接或者间接找到到该对象在“堆内存”中数据存放的起始地址索引;
    • 直接或者间接找到对象所属数据类型在方法区中存储的类型信息;
  • 局部变量表建立在线程的堆栈上,所以操作两个连续的slot是否为原子操作,都不会引起数据安全问题,但是如果是64位的话,不允许任何方式单独访问其中的一个;
  • this:实例方法(非static)默认第一个(第0位索引)slot为当前对象自己的引用;
  • slot重用:
    • 当前字节码的pc计数器超出某个变量的作用域,那这个变量的slot可以交给别的变量使用;
    • 影响到正常的Java垃圾回收机制;
  • 赋null:因为上述slot重用的原因,当方法域内前面有局部变量定义了大内存实际不再使用的变量,紧接着后面的代码又是一个耗时的操作,这个时候及时赋null就显得有大的意义。因为一旦触发后,这部分的slot就可以被重用了。看起来就像是方法区内部进行“类gc"操作一样。但是,并不是任何时候都要进行赋null.以恰当的变量作用域来控制变量回收时间才是最优雅的方式,并且赋null值操作在经过JIT编译优化后会被消除掉,这样的话实际是没有任何意义的。
  • 初始值:和类变量不同,局部变量系统不会自动赋初始值,所以没有赋值是无法使用的,编译都无法通过。即使通过,字节码校验阶段也会检查出来而导致类加载失败;

3、操作数栈(Operand Stack)

  • 操作栈,后入先出;
  • 最大深度:Code属性表中的max_stacks;
  • 32位数据类型所占栈容量为1,64位所占容量为2;
  • 栈元素的数据类型必须和栈指令保持一致
  • 两个栈帧之间可以存在一部分的重叠,共享数据,这样在方法调用的时候避免的额外的参数复制。
  • Java虚拟机的解释执行引擎也是:基于栈的执行引擎;

4、动态连接(Dynamic Linking)
字节码中的方法的调用都是通过常量池中指定方法的符号作为参数

  • 静态解析:这种符号有的是类加载阶段或者首次使用初始化的时候转化为直接的引用
  • 动态连接:另外一部分是在运行时转化为直接引用

5、方法返回地址

  • 退出:

    • 正常退出:遇到返回的字节码指令;
    • 异常退出:本方法异常表中没有匹配的异常;
  • 退出后,恢复上层方法的局部变量表和操作栈,有返回值就把返回值压入上层调用者的栈中;

三.方法调用

定义:确定被调用方法的版本
1、解析

  • 编译器可知,运行期不可变。这类方法的调用成为解析,在类加载阶段进行解析。
  • 静态方法、私有方法、实例构造器方法、父类方法,符合上述条件。特点是:
    • 只能被invokestatic和invokespecial指令调用
    • 不可继承或者重写,编译时已经确定了一个版本。
    • 在类加载时会把符合引用解析为该方法的直接引用。
    • 非虚方法(注意final也是非虚方法,其他的都是虚方法)

2、静态分派

  • 概念:根据静态类型来定位方法的执行版本
  • 典型代表:方法的重载(方法名相同,参数类型不同)
  • 发生时间:编译阶段

3、动态分派

  • 概念:调用invokevirtual时,把常量池中的类方法符号解析到了不同的直接引用上。
  • 典型代表:重写,多态的重要体现
  • 过程:
    • 执行invokevitual指令
    • 在虚方法表(类加载阶段,类变量初始化结束后会初始化虚方法表)中查找方法,没有向上的父类进行查找
  • 方法宗量:方法的接收者与方法参数的总称
  • 单分派和多分派:
    • 只有一个宗量作为方法的选择依据,称为单分派。多个,则称为多分派。
    • 当前的Java是静态多分派、动态单分派的语言;

四.动态语言支持

  • 特点:变量无类型,变量的值才有类型

  • invoke包:Java实现动态语言新增的包

五.指令集

  • 基于栈的指令集

    • 过程:入栈、计算、出栈
    • 优点:
      • 可移植性,不依赖于硬件
      • 代码紧凑
    • 缺点:
      • 速度较慢
      • 产生相当多的指令数量
      • 频繁内存访问
  • 基于寄存器的指令集
    • 代表:x86

六.方法内联

  • 方法内联的方式是通过吧“目标方法”的代码复制到发起调用的方法内,避免真实的方法调用。
  • 内联消除了方法调用的成本,还为其他优化手段建立良好的基础。
  • 编译器在进行内联时,如果是非虚方法,那么直接内联。如果遇到虚方法,则会查询当前程序下是否有多个目标版本可供选择,如果查询结果只有一个版本,那么也可以内联,不过这种内联属于激进优化,需要预留一个逃生门(Guard条件不成立时的Slow Path),称为守护内联。
  • 如果程序的后续执行过程中,虚拟机一直没有加载到会令这个方法的接受者的继承关系发现变化的类,那么内联优化的代码可以一直使用。否则需要抛弃掉已经编译的代码,退回到解释状态执行,或者重新进行编译

七.逃逸分析

逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法里面被定义后,它可能被外部方法所引用,这种行为被称为方法逃逸。被外部线程访问到,被称为线程逃逸。
如果对象不会逃逸到方法或线程外,可以做什么优化?

  • 栈上分配:一般对象都是分配在Java堆中的,对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问堆中存储的对象数据。但是垃圾回收和整理都会耗时,如果一个对象不会逃逸出方法,可以让这个对象在栈上分配内存,对象所占用的内存空间就可以随着栈帧出栈而销毁。如果能使用栈上分配,那大量的对象会随着方法的结束而自动销毁,垃圾回收的压力会小很多。
  • 同步消除:线程同步本身就是很耗时的过程。如果逃逸分析能确定一个变量不会逃逸出线程,那这个变量的读写肯定就不会有竞争,同步措施就可以消除掉。
  • 标量替换:不创建这个对象,直接创建它的若干个被这个方法使用到的成员变量来替换。

八.小结

  在前面我们已经了解到栈帧、方法区的内存时线程私有的,本篇更加详细的讲了方法是怎么找到并执行的。Java虚拟机规范:输入字节码,解析字节码处理,输出结果。首先,栈帧包含了局部变量表、操作数栈、动态连接、方法返回地址。字节码中的方法都是通过常量池中的符号作为参数指定的,有些编译解析确定,有些运行行时转化为直接引用。首先记住,JVM是基于栈的执行引擎。栈有着先入后出的特点,执行引擎的指令也仅执行当前栈。而局部变量表存储了方法内需要的变量信息,是以Slot 为单位进行存储,超出操作域后,原本占用的内存区域可以被其他的局部变量使用,类似“回收”。然后,记住Java是静态多分派,动态单分派的语言。静态分派,如方法的重载。通过方法的参数不同就可以确定要调用哪个方法,这个再编译阶段就定好。动态分派,如方法的重写。执行方法时,有一个虚方法表。这这个表里搜索,自己有就执行自己的,没有向上找父类的。这个是Java实现多态的重要原理。Java也有支持动态语言的invoke包,平时用的较少。

深入理解Java虚拟机06--虚拟机字节码执行引擎的更多相关文章

  1. 深入理解java:1.2. 字节码执行引擎

    执行引擎是Java虚拟机的核心组成部分之一. 首先,想想C++和Java在编译和运行时到底有啥不一样? 下图左边,C++发布的就是机器指令, 而下图右边Java发布的是字节码,字节码在运行时通过JVM ...

  2. JVM虚拟机(二):字节码执行引擎

    运行时栈帧结构     栈帧是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈的栈元素.栈帧存储了方法的局部变量表.操作数栈.动态链接.和方法返回地址等信息. ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

随机推荐

  1. 爬虫不过如此(python的Re 、Requests、BeautifulSoup 详细篇)

    网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本. 爬虫的本质就是一段自动抓取互联网信息的程序,从网络获取 ...

  2. Docker面试题

    1.如何列出可运行的容器?docker ps 2.启动nginx容器(随机端口映射),并挂载本地文件目录到容器html的命令是?docker run -d -P --name nginx2 -v /h ...

  3. .Net #if DEBUG调试模式代码使用

    #if DEBUG      Console.WriteLine("DEBUG:11111111111"); #else       Console.WriteLine(" ...

  4. vue路由对不同界面进行传参及跳转的总结

    最近在做一个公众号的商城项目,主要用的VUE+MUI,其实今天这个点对于有过项目经验的前端工作者来说是最基础的,但也是必须要掌握的,今天小编主要是记录下传参和跳转的一些总结(仅供参考). 首先我们先上 ...

  5. for循环输出空心菱形的形状【java】

    使用for循环语句输出以下“空心菱形”效果: * * * * * * * * * * * * * * * * 建议优先参考笔者的另一篇文章:<for循环输出菱形的形状[java]> 代码: ...

  6. DWR第六篇之文件下载

    1. 在第五篇架构基础上进行修改 2. 修改jsp页面 <html> <head> <base href="<%=basePath%>"& ...

  7. k8s之external-etcd集群管理

    一.概述 kubernetes使用etcd作为数据中心,使用kubeadm部署kubernetes的时候默认会自己部署一个etcd,当然也可以将kubeadm部署的单点的etcd做成集群,但是比较麻烦 ...

  8. python 加密算法及其相关模块的学习(hashlib,random,string,math)

    加密算法介绍 一,HASH Hash,一般翻译做“散列”,也有直接音译为”哈希”的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值.这种 ...

  9. Jenkins自动化构建配置

    写在前头 我每次修改代码一点东西,都要进行一个重新发布.重新发布的流程大概如下: 将最新代码同步上传到git上面 Maven打包 mvn clean install 将最新的jar包上传到服务器上面, ...

  10. Perl的输出:print、say和printf、sprintf

    print.printf和say都可以输出信息.print和say类似,print不自带换行符,say自带换行符,但要使用say,必须写use语句use 5.010;,printf像C语言的print ...