【深入理解JAVA虚拟机】第三部分.虚拟机执行子系统.3.函数调用与执行
这章原名叫“虚拟机字节码执行引擎”,实际就是讲的函数如何调用和执行的。
1、概述
“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,
其区别是物理机的执行引擎是直接建立在处理器、 硬件、 指令集和操作系统层面上的,
而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。
在Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,这个概念模型成为各种虚拟机执行引擎的统一外观(Facade)。
在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择[1],也可能两者兼备,甚至还可能会包含几个不同级别的编译器执行引擎。
但从外观上看起来,所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果
2、运行时栈帧结构
2.1 什么是栈帧
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)[1]的栈元素。
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
每一个栈帧都包括了局部变量表、 操作数栈、 动态连接、 方法返回地址和一些额外的附加信息。
在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中[2],
因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。
对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法(Current Method)。
执行引擎运行的所有字节码指令都只针对当前栈帧进行操作
2.2、栈帧结构图
2.3、栈帧的详细内容
每一个栈帧都包括了局部变量表、 操作数栈、 动态连接、 方法返回地址和一些额外的附加信息。
局部变量表(Local Variable Table)
一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。容量以变量槽(Variable Slot,下称Slot)为最小单位 。
虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的Slot数量。
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,
如果执行的是实例方法(非static的方法),那局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过关键字“this”来访问到这个隐含的参数。
其余参数则按照参数表顺序排列,占用从1开始的局部变量Slot,参数表分配完毕后,再根据方法体内部定义的变量顺序和作用域分配其余的Slot。
操作数栈(Operand Stack)
操作数栈也常称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。 同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。
当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。
举个例子,整数加法的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。
动态连接 (Dynamic Linking)
每个栈帧都包含一个指向运行时常量池[1]中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
方法返回地址
方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:
恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
附加信息
虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧之中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现。
3、方法调用
注:这里的调用只包含调用,不包含执行。
3.1、解析(Resolution)
确定方法调用目标的过程,就是解析的过程。
JVM调用字节码指令有5种:
invokestatic:调用静态方法。
invokespecial:调用实例构造器<init>方法、 私有方法和父类方法。
invokevirtual:调用所有的虚方法。
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
解析调用一定是个静态的过程,在编译期间就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。
3.2、分派(Dispatch)
分派(Dispatch)调用则可能是静态的也可能是动态的,根据分派依据的宗量数[1]可分为单分派和多分派。
静态分派
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。
静态分派的典型应用是方法重载。
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
举例:
class A ;
class B extern A;
void call(A a);
void call(B b);
A a = new B(); // 这里明确两个名词: 对象a的静态类型(A),动态类型(B)。
call(a); // 会调用void call(A a); 因为重载是在编译时决定的,重载决定的要素是静态类型,编译期未运行,不可能是运行态(动态)。
另外,对于原始数据类型,静态分派时会进行自动转换,转换顺序为:char->int->long->float->double ->装箱->Object->可变参数列表(最后)
动态分派
和多态性的另外一个重要体现[3]——重写(Override)有着很密切的关联。
显然的,静态分派的优先级要高于动态分派,因为静态分派是在编译期就已经确定了的。
总结一句:今天的Java语言是一门静态多分派、 动态单分派的语言。单分派是指已经确定了类型,只是看选父类还是子类了。
3.3、动态类型语言
动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期
Java1.7引入了invokedynamic 、MethodHandle ,但仍然不支持动态语言,详略。
4、方法执行
Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择。
在本章中,我们先来探讨一下在解释执行时,虚拟机执行引擎是如何工作的。
4.1 解释执行
Javac编译器完成了程序代码经过词法分析、 语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。
因为这一部分动作是在Java虚拟机之外进行的,而解释器在虚拟机的内部,所以Java程序的编译就是半独立的实现。
4.2 基于栈的指令集与基于寄存器的指令集
Java编译器输出的指令流,基本上[1]是一种基于栈的指令集架构(Instruction Set Architecture,ISA),指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作。
与之相对的另外一套常用的指令集架构是基于寄存器的指令集,最典型的就是x86的二地址指令集,说得通俗一些,就是现在我们主流PC机中直接支持的指令集架构,这些指令依赖寄存器进行工作。
那么,基于栈的指令集与基于寄存器的指令集这两者之间有什么不同呢?
基于栈的:用内存做栈,存临时对象,计算时要压栈出栈。
基于寄存器的:用硬件提供的寄存器存临时对象,计算式直接使用。
对于“1+1”这个计算:
基于栈的命令:
iconst_1
iconst_1
iadd
istore_0
基于寄存器的命令:
mov eax,1
add eax,1
优缺点:
基于栈的指令集主要的优点就是可移植, 缺点是执行速度相对来说会稍慢一些。 寄存器则相反。所有主流物理机的指令集都是寄存器架构 。
【深入理解JAVA虚拟机】第三部分.虚拟机执行子系统.3.函数调用与执行的更多相关文章
- 深入理解Java内存模型中的虚拟机栈
深入理解Java内存模型中的虚拟机栈 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都会有各自的用途,以及创建和销毁的时间,有的区域会随着虚拟机进程的启 ...
- (4) 深入理解Java Class文件格式(三)
转载:http://blog.csdn.net/zhangjg_blog/article/details/21557357 首先, 让我们回顾一下关于class文件格式的之前两篇博客的主要内容. 在 ...
- 【由浅入深理解java集合】(三)——集合 List
第一篇文章中介绍了List集合的一些通用知识.本篇文章将集中介绍List集合相比Collection接口增加的一些重要功能以及List集合的两个重要子类ArrayList及LinkedList. 一. ...
- 【深入理解JAVA虚拟机】第三部分.虚拟机执行子系统.4.类加载及执行子系统的案例与实战
1.概述 在Class文件格式与执行引擎这部分中 : 用户不能控制的:Class文件以何种格式存储,类型何时加载. 如何连接,以及虚拟机如何执行字节码指令等都是由虚拟机直接控制的行为 用户能控制的:字 ...
- java虚拟机(三)-- 虚拟机类加载机制
1.类加载的时机:类从被加载到虚拟机内存中开始,到卸载出内存为止.包含以下几个阶段: 1.加载 2.验证 3.准备 4.解析 5.初始化 6.使用 7.卸载 2.类加载器的种类 1.启动类加载器:这个 ...
- 深入理解Java AIO(三)—— Linux中的AIO实现
我们调用的Java AIO底层也是要调用OS的AIO实现,而OS主要也就Windows和Linux这两大类,当然还有Solaris和mac这些小众的. 在 Windows 操作系统中,提供了一个叫做 ...
- 深入理解JAVA集合系列三:HashMap的死循环解读
由于在公司项目中偶尔会遇到HashMap死循环造成CPU100%,重启后问题消失,隔一段时间又会反复出现.今天在这里来仔细剖析下多线程情况下HashMap所带来的问题: 1.多线程put操作后,get ...
- Java虚拟机内存溢出异常--《深入理解Java虚拟机》学习笔记及个人理解(三)
Java虚拟机内存溢出异常--<深入理解Java虚拟机>学习笔记及个人理解(三) 书上P39 1. 堆内存溢出 不断地创建对象, 而且保证创建的这些对象不会被回收即可(让GC Root可达 ...
- 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略
垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...
随机推荐
- android 小工具:pc 上用 curl 命令打开手机浏览器,浏览指定网址
测试 API 时或其它情况经常需要在手机浏览器中输入 url 一长串的 url 输起来真是麻烦 AirDroid 很强大也不用数据线,但有时老断开连接,不是很爽.发到手机 qq 吧还得手动粘贴 所以自 ...
- 如何正确且高效地中文汉化Spyder 2 或 Spyder3(图文详解)(博主推荐)
不多说,直接上干货! 汉化下载和教程页面 : https://github.com/kingmo888/Spyder_Simplified_Chinese 汉化文件最新版直接下载 : https: ...
- Mac开发利器之程序员编辑器MacVim学习总结(转)
一.关于Vim Emacs和Vim都是程序员专用编辑器,Emacs被称为神的编辑器,Vim则是编辑器之神.至于两者到底哪个更好用,网络上两大派系至今还争论不休.不过,相比之下,Emacs更加复杂, ...
- c#随便写写 数据层和表现层,队列执行
base.xxx() 调用父类的方法
- hibernate 学习笔记1
Hibernate session1 1.连接池的最小连接数指的是连接池初始化之后,就存在的连接数,这些连接放在内存中,等待被使用.最大连接数限定了连接池中最大同时连接数量,如果超过了这个数量,则进入 ...
- java中wait和notify的关系
java中,wait和notify这两个方法是一对,wait方法阻塞当前线程,而notify是唤醒被wait方法阻塞的线程. 首先,需要说明的是,wait和notify方法都是Object的实 ...
- C# dynamic json
对应普通对象,写个扩展方法,ToJson蛮方便. 但是 dynamic 类型就不行了,因为是运行时解析,只能转换为强类型 IDictionary<string, object> 才可以. ...
- heroku快速部署node应用
试了一下heroku,简直碉堡了,下面介绍如何简单几步实现弄得应用的部署访问: 1.首先https://dashboard.heroku.com/进行账号注册 2.github上push一个最新的no ...
- cygwin 的安装和配置
Cygwin是一个在windows平台上运行的类UNIX模拟环境,是cygnus solutions公司开发的自由软件(该公司开发的著名工具还有eCos,不过现已被Redhat收购).它对于 ...
- PHP 字符串常用操作
1,拼接字符串 拼接字符串是最常用到的字符串操作之一,在PHP中支持三种方式对字符串进行拼接操作,分别是圆点.分隔符{}操作,还有圆点等号.=来进行操作,圆点等号可以把一个比较长的字符串分解为几行进行 ...
