五 虚拟机字节码执行引擎
 
1 运行时栈帧结构
    ---栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素。
    ---栈帧中存储了方法的局部变量表、操作数栈、动态连接、方法返回地址和一些附加信息。
    ---每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。
    ---在编译程序代码的时候,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中。
    (1)局部变量表
            ---是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
            ---局部变量表的最大容量在编译时写入到Code属性的max_locals数据项中,方法执行的任意时刻,局部变量表的容量都不会大于max_locals数据项中设定的最大容量。
            ---容量以变量槽(Slot)为最小单位,每个Slot能存放一个32位以内的数据类型,包括boolean、byte、char、short、int、float、reference或returnAddress类型的数据。
            ---reference类型表示对一个对象实例的引用。虚拟机规范既没有说明它的长度,也没有指出它的结构,但虚拟机实现至少能通过这个引用做到:
                · 从此引用中直接或间接查找到对象在Java堆中的数据存放的起始地址索引;
                · 从此引用中直接或间接查找到对象所属数据类型在方法区中的存储的类型信息。
            ---returnAddress类型指向了一条字节码指令的地址。
            ---对于64位数据类型(long、double),虚拟机会以高位对齐的方式为其分配两个连续的Slot空间。
            ---虚拟机通过索引定位的方式使用局部变量表,范围为0至局部变量表最大的Slot数量。        
            ---在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法,那局部变量表的第0位索引的Slot默认用于传递方法所属对象实例的引用(“this”),其余参数按照参数表顺序排列,占用从1开始的局部变量Slot。参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot。
            ---局部变量表中的Slot是可以重用的。重用可以节省栈空间,但可能带来一些副作用,如可能会影响系统的垃圾收集行为。
            ---方法中,当一个变量不再使用时,在对象占用内存大、此方法的栈帧长时间不能被回收、方法调用次数达不到JIT的编译条件的情形下,应将该变量手动赋值为null。
            ---局部变量必须在赋值后才能使用。
    (2)操作数栈
            ---操作数栈中的每一个元素可以是任意的Java数据类型。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。
            ---操作数栈的最大深度在编译时写入到Code属性的max_stack数据项中,方法执行的任意时刻,操作数栈的深度都不会超过在max_stack数据项中设定的最大值。
            ---操作数栈中元素的数据类型必须与字节码指令的序列严格匹配。编译时,编译器要严格保证这一点;在类校验阶段的字节码校验阶段还要再次验证这一点。
    (3)动态连接
                ---每个栈帧中都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
    (4)方法返回地址
                ---当一个方法开始执行后,只有两种方式可以退出这个方法:
                    · 正常完成出口:执行引擎遇到任意一个方法返回的字节码指令。可能会有返回值传递给上层的方法调用者。
                    · 异常完成出口:方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理。不会给上层调用者产生任何返回值。
                ---方法退出后,需要返回到方法被调用的位置。一般来说,
                    · 方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能保存该计数器值;
                    · 而方法异常退出时,返回地址要通过异常处理器表来确定,栈帧中一般不会保存这部分信息。
                ---方法退出时可能执行的操作:
                    · 恢复上层方法的局部变量表和操作数栈;
                    · 把返回值(如果有的话)压入调用者栈帧的操作数栈中;
                    · 调整PC计数器的值以指向方法调用指令后面的一条指令等。
 
2 方法调用
        ---确定被调用方法的版本。
    (1)解析
            ---要求:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。即“编译期可知,运行期不可变”。
            ---在类加载阶段进行解析,即在类加载的解析阶段,会将这类方法的符号引用转化为直接引用。
            ---主要包括:静态方法和私有方法。前者与类型相关联,后者在外部不可被访问,因此它们都不可能通过继承或者别的方式重写其他版本。
            ---其次还包括:实例构造器<init>()方法、父类方法以及被final修饰的方法。
            ---与之相对应的两条方法调用的字节码指令:invokestatic、invokespecial。
            ---虚方法和非虚方法:
                · 非虚方法:静态方法、私有方法、实例构造器、父类方法、被final修饰的实例方法;
                · 虚方法:没有被final修饰的实例方法。
    (2)分派
            ---可能是静态的,也可能是动态的。
            ---根据分派依据的宗量数可分为单分派和多分派。
            ---解析与分派两者之间的关系并不是二选一的排他关系,它们是在不同层次上去筛选、确定目标方法的过程。如静态方法会在类加载期就进行解析,而静态方法选择重载版本的过程也是通过静态分派完成的。
            1)静态分派
                ---所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。
                ---静态类型和实际类型的区分如下:Human man = new Man();其中Human是静态类型,Man是实际类型。
                ---静态类型和实际类型在程序中都可以发生一些变化,区别是:
                    · 静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;
                    · 实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。
                        
                ---典型应用:方法重载。编译器在重载时是通过参数的静态类型而不是实际类型作为判定依据的,并且静态类型在编译器是可知的。
                ---静态分派发生在编译阶段,因此确定静态分派的动作实际上不是有虚拟机来执行的,而是通过编译器。
                ---编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是“唯一的”,往往只能确定一个“更加合适的”版本。产生这种模糊结论的主要原因是字面量不需要定义,所以字面量没有显式的静态类型,它的静态类型只能通过语言上的规则去理解和推断。
                ---重载匹配优先级:字面量'a'的匹配:char-int-long-float-double-Character(Integer等版本不能匹配)-Serializable(或Comparable,接口版本,不能同时出现多个)-Object(父类版本)-char ...(变长参数版本)-int ...-long ...-float ...-double ...。
            2)动态分派
                ---在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
                ---典型应用:重写。
                ---invokevirtual指令的运行时解析过程:
                    · 找到操作数栈栈顶的的第一个元素所指向的对象的实际类型,记作C。
                    · 如果在类型C中找到与常量中的描述符和简单名称都一致的方法,则进行访问权限校验,若通过则返回这个方法的直接引用,查找过程结束;若不通过,则返回java.lang.IllegalAccessError异常。
                    · 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
                    · 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
                ---重写的本质:由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上。
            3)单分派和多分派
                ---方法的接收者与方法的参数统称为方法的宗量。
                ---根据分派基于多少种宗量,可以把分派划分为单分派和多分派两种。单分派是指根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。
                ---静态多分派、动态单分派:Java语言的静态分派属于多分派类型,动态分派属于单分派类型。
            4)虚拟机动态分派的实现
                ---“稳定优化”手段:在类的方法区中建立一个虚方法表(相对应的有接口方法表),使用虚方法表索引来代替元数据查找以提高性能。
                ---方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。
                ---“激进优化”手段:内联缓存和基于“类型继承关系分析”技术的守护内联。
 
3 动态类型语言支持
    (1)动态类型语言
            ---动态类型语言的关键特征:它的类型检查的主体过程是在运行期而不是编译期,如Python、Ruby等。
            ---动态类型语言的另一个重要特征:变量无类型而变量值才有类型。
            ---静态类型语言的关键特征:在编译器就进行类型检查过程,如C++、Java等。
            ---动态类型语言优点:清晰和简洁。
            ---静态类型优点:利于稳定性及代码达到更大规模。
    (2)java.lang.invoke包
            ---该包的主要目的是在之前单纯依靠符号引用来确定调用的目标方法这种方式以外,提供一种新的动态确定目标方法的机制,称为MethodHandle。
            ---从Java语言的角度来看,MethodHandle的使用方法和效果与Reflection有众多相似之处,它们的区别是:
                · 从本质上讲,Reflection和MethodHandle的机制都是在模拟方法调用,但Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用。
                · Reflection是重量级,而MethodHandle是轻量级。前者包含了方法的签名、描述符、以及方法属性表中各种属性的Java端表示方式,还包含了执行权限等运行期信息;而后者仅仅包含与执行该方法相关的信息。
                · 理论上,虚拟机在字节码层次的方法指令调用上做的各种优化(如方法内联),在MethodHandle上也应当可以采用类似思路去支持;而通过反射去调用方法则不行。
                · Reflection API的设计目标是只为Java语言服务的,而MethodHandle则设计成可服务于所有Java虚拟机之上的语言。
    (3)invokedynamic指令
            略
 
4 基于栈的字节码解释执行引擎
​    ---在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择。
    ---Java虚拟机的解释执行引擎称为“基于栈的执行引擎”。
    (1)解释执行
            
    (2)基于栈的指令集与基于寄存器的指令集
            ---Java编译器输出的指令流,基本上是一种基于栈的指令集架构,指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作。
            ---基于栈的指令集
                · 主要的优点是:可移植、代码相对更加紧凑、编译器实现更加简单(不需要考虑空间分配问题,所需空间都在栈上操作)等。
                · 主要缺点:执行速度相对来说会稍慢一些(指令数量多、内存访问慢)。
 

深入理解Java虚拟机读书笔记5----虚拟机字节码执行引擎的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Java虚拟机--虚拟机字节码执行引擎 所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 用于支持虚拟机进行方法调用和方 ...

  9. 深入了解java虚拟机(JVM) 第十三章 虚拟机字节码执行引擎

    一.概述 执行引擎是java虚拟机最核心的组成部件之一.虚拟机的执行引擎由自己实现,所以可以自行定制指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式.所有的Java虚拟机的执行 ...

随机推荐

  1. 服务器、应用框架、MVC、MTV

    web服务器:负责处理http请求,响应静态文件,常见的有Apache,Nginx以及微软的IIS. 应用服务器:负责处理逻辑的服务器.比如php.python的代码,是不能直接通过nginx这种we ...

  2. vue.js学习第一天,了解vue.js

    vue.js是一个前端框架,他与jquery不同,jquery是一个js库,而vue.js是js的前端框架. vue.js对我们前端页面入侵比较大,而jquery则不然.那我们还为什么要使用vue.j ...

  3. 3,列表的 深 浅 copy

    如果列表只有一层,深浅copy是一样一样的,没有什么区别,你修改了copy后的列表,copy前的列表并不会随之改变. 如果列表中嵌套这列表,这是你修改了copy后第二层列表里面的元素,copy前第二层 ...

  4. python padas 学习

    import matplotlib from pandas import DataFrame import numpy as np import pandas as pd import MySQLdb ...

  5. RGB颜色名称与色值对应表

    实色效果 英文名称 R.G.B 16色 实色效果 英文名称 R.G.B 16色   Snow 255 250 250 #FFFAFA   PaleTurquoise1 187 255 255 #BBF ...

  6. uclibc,eglibc,glibc,Musl-libc之间的区别和联系

    转自:https://www.sohu.com/a/164202127_424963 1.Glibc glibc = GNU C Library 是GNU项(GNU Project)目,所实现的 C语 ...

  7. 18.13 Uboot分析与移植

    18.13.1 使用JLink烧写Nor Flash JLink只支持烧写NOR Flash,不支持烧写Nand Flash. 1.准备工作:JLink的USB口接到电脑上,JLink的JTAG口用排 ...

  8. webAPP如何实现移动端拍照上传(Vue组件示例)?

    摘要:使用HTML5编写移动Web应用,主要是为了尝试一下“一套代码多处运行”,一个webapp几乎可以不加修改的运行在PC/Android/iOS等上面运行.但是写到现在觉得虽然这种方式弊大于利,不 ...

  9. Vmware虚拟中克隆主机没IP地址?怎么解决?

    Vmware虚拟中克隆主机没IP地址?怎么解决? 修改网卡的配置文件:  清空如下的文件: 重启主机即可!  

  10. php如何和linux进行通讯

    FastCGI原理 FastCGI是一个运用于Http Server和动态脚本语言间通信的接口,多数流行的Http Server都支持FastCGI,包括Apache.Nginx和lighttpd等. ...