《深入理解Java虚拟机》读书笔记6

第8章 虚拟机字节码执行引擎
8.1
1、从外观上看,所有Java虚拟机的执行引擎是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。
2、虚拟机与物理机
物理机的执行引擎:是直接建立在处理器、硬件、指令集和操作系统层面的。
虚拟机的执行引擎:是自己实现的,因此可以自行制定指令集和执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。

8.2运行时栈帧结构
1、栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
2、栈帧存储了:方法的局部变量表、操作数栈、动态连接、方法返回地址等信息。
3、
每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
4、对于Java引擎来讲,活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,这个栈帧所关联的方法称为当前方法。执行引擎所运行的所有字节码指令都是针对当前栈帧进行操作的。
局部变量表:
一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。(java程序被编译为Class文件时,就在方法的Code的属性
max_locals
确定了该方法所要分配的最大局部变量表的容量)
以变量槽(Slot)为最小单位,Slot可重用。
虚拟机通过索引定位的方式使用局部变量表,索引值范围从0到局部变量表最大的Slot数量。
操作数栈(操作栈):
后入先出栈。
最大深度也在编译的时候被写入到Code属性的
max_stacks
数据项中。
操作数栈的每一个元素可以是任意的Java数据类型。
动态连接:
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接,持有这个引用是为了支持方法调用过程中的动态连接。
方法返回地址:
当一个方法被执行后,有两种退出方式:
1)正常完成出口:执行引擎遇到任意一个方法返回的字节码指令,可能会有返回值传递给上层的方法调用者。
2)异常完成出口:在方法执行中遇到了异常,并且该异常没有在方法体内得到处理。
无论采用何种退出方式,在方法退出后,都要返回到方法被调用的位置。

8.3方法调用
1、方法调用:方法调用不等同于方法执行,方法调用唯一的任务是
确定被调用方法的版本
(调用哪个方法),暂时还不涉及方法内部的具体运行过程。
2、Class文件的编译过程不包含传统编译的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址。这使得Java有强大的动态扩展能力,但使Java方法的调用过程变得相对复杂,需要在类加载期间甚至到运行时才能确认目标方法的直接引用。(类加载的解析)
3、解析成立的前提:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期间是不可变的。(编译期可知,运行期不可变)
4、Java虚拟机调用字节码指令的4条方法:

  • invokestatic:调用静态方法
  • invokespecial:调用实例构造器<init>方法、私有方法和父类方法
  • invokevirtual:调用所有的虚方法
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象

补充:
1)静态方法、实例构造器<init>方法、私有方法和父类方法可以在类加载的时候把符号引用解析为该方法的直接引用,这些方法称为非虚方法。
2)虽然
final方法
是使用invokevirtual指令调用的,但它也是非虚方法。原因:由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,或者说选择结果是唯一的。
5、解析调用与分派调用
解析调用(Resolution):一定是个静态过程,在编译期就完全确定,类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再完成。
分派调用(Dispatch):可能是静态的或者动态的。
6、分派调用分类:静态单分派、静态多分派、动态单分派、动态多分派
7、静态分派与动态分派的定义
静态分派:所有依赖静态类型来定位方法执行版本的分派动作。(最典型应用方法重载)
补充:Human man = new Man();中”Human”称为变量的
静态类型
或者外观类型;”Man”称为变量的
实际类型

动态分派:在运行期间根据实际类型确定方法版本的分派(与重写有密切关联)
8、单分派和多分派
补充:方法的接收者和方法的参数统称为
宗量

单分派:根据一个宗量对目标方法进行选择;
多分派:根据多于一个的宗量对目标方法进行选择。

8.4基于栈的字节码解释执行引擎
(介绍虚拟机是如何执行方法里面的字节码指令的)
1、解释执行(通过解释器执行)  编译执行(通过即时编译器产生本地代码)
2、
解释执行
当主流的虚拟机中都包含了即时编译器后,Class文件中的代码到底会被解释执行还是编译执行,只有虚拟机自己才能准确判断。
《《深入理解Java虚拟机》读书笔记6》

Javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。因为这一动作是在Java虚拟机之外进行的,而解释器在虚拟机的内部,
所以Java程序的编译是半独立的实现。
3、(以下内容为网上转载内容)
什么是基于栈的指令集?
Java编译器输出的指令流,里面的指令大部分都是零地址指令,它们依赖
操作数栈
进行工作。
计算“1+1=2”,基于栈的指令集是这样的:
iconst_1iconst_1iaddistore_0
两条iconst_1指令连续地把两个常量1压入栈中,iadd指令把栈顶的两个值出栈相加,把结果放回栈顶,最后istore_0把栈顶的值放到局部变量表的第0个Slot中。
什么是基于寄存器的指令集?
最典型的是x86的地址指令集,依赖寄存器工作。 
计算“1+1=2”,基于寄存器的指令集是这样的:
mov eax, 1add eax, 1
mov指令把EAX寄存器的值设为1,然后add指令再把这个值加1,结果就保存在EAX寄存器里。
基于栈的指令集的优缺点?
优点:

  • 可移植性好:用户程序不会直接用到这些寄存器,由虚拟机自行决定把一些访问最频繁的数据(程序计数器、栈顶缓存)放到寄存器以获取更好的性能。
  • 代码相对紧凑:字节码中每个字节就对应一条指令
  • 编译器实现简单:不需要考虑空间分配问题,所需空间都在栈上操作

缺点:

  • 执行速度稍慢
  • 完成相同功能所需的指令熟练多

频繁的访问栈,意味着频繁的访问内存,相对于处理器,内存才是执行速度的瓶颈。

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/weixin_40388416/article/details/80297441
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞