《深入理解java虚拟机》-虚拟机字节码执行引擎

在java虚拟机规范中定制了虚拟机字节码执行引擎的概念模型,这个概念模型成为各种虚拟机执行引擎的统一外观(Facade)。从外观上看,所有java虚拟机的执行引擎都是一致的:输入字节码文件,输出执行结果

运行时帧栈结构

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。典型栈帧结构:

栈帧的概念结构

局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机规范中并没有明确指定一个Slot应占用的内存空间大小,只是规定每个Slot都应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据,这样可以屏蔽32位跟64位虚拟机在内存空间上的差异。

虚拟机通过索引定位的方式使用局部变量表,索引值的范围从0到最大Slot数量,索引n对应第n个Slot。局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用,即this。

为了尽可能的节省栈帧空间,局部变量表中的Slot是可以重用的,同时这也影响了垃圾收集行为。即对已使用完毕的变量,局部变量表仍持有该对象的引用,导致对象无法被GC回收,占用大量内存。这也是“不使用的对象应手动赋值为null”这条推荐编码规则的原因。不过从执行角度使用赋null值的操作来优化内存回收是建立在对字节码执行引擎概念模型的理解之上,代码在经过编译器优化后才是虚拟机真正需要执行的代码,这时赋null值会被消除掉,因此更优雅的解决办法是以恰当的变量作用域来控制变量回收时间。

操作数栈

操作数栈(Operand Stack)也常称操作栈,它是一个后入先出(Last In First Out,LIFO)栈。方法在执行过程中,通过各种字节码指令对栈进行操作,出栈/入栈。java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈

动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用时为了执行方法调用过程中的动态连接(Dynamic Linking)

方法返回地址

当一个方法开始执行后,只有两种方式可以退出这个方法:

  1. 执行引擎遇到任意一个方法返回的字节码指令,这个时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),这种退出方式称为正常完成出口(Normal Method Invocation Completion)
  2. 方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是java虚拟机内部产生的异常,还是代码使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion),这时不会给它的上层调用者产生任何返回值

方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:

  • 恢复上层方法的局部变量表和操作数栈
  • 把返回值(如果有)压入调用者栈帧的操作数栈
  • 调整PC计数器的值以指向方法调用指令后面的一条指定等

附加信息

虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,称之为栈帧信息

方法调用

方法调用并不等同于方法执行,方法调用阶段的唯一任务就是确定被调用方法的版本,即调用哪一个方法,暂时还不涉及方法内部的具体运行过程,就是类加载过程中的类方法解析。

解析

解析就是将Class的常量池中的符号引用转化为直接引用。在java虚拟机中提供了5条方法调用字节码指令:

  • invokestatic:调用静态方法

    System.exit(1);
    ==>编译
    iconst_1    ;将1放入栈内
                ;执行System.exit()
    invokestatic java/lang/System/exit(I)V
    
  • invokespecial:调用实例构造器<init>方法、私有方法和父类方法

    //<init>方法
    new StringBuffer()
    ==>编译
    new java/lang/StringBuffer    ;创建一个StringBuffer对象
    dup                           ;将对象弹出栈顶
                                  ;执行<init>()来初始化对象
    invokespecial java/lang/StringBuffer/<init>()V
    
    //父类方法
    super.equals(x);
    ==>编译
    aload_0   ;将this入栈
    aload_1   ;将第一个参数入栈
              ;执行Object的equals()方法
    invokespecial java/lang/Object/equals(Ljava/lang/Object;)Z
    
    //私有方法
    与父类方法类似
    
  • invokevirtual:调用所有的虚方法

    X x;
    ...
    x.equals("abc");
    ==>编译
    aload_1   ;将x入栈
    ldc "abc"   ;将“abc”入栈
              ;执行equals()方法
    invokevirtual X/equals(Ljava/lang/Object;)Z
    
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象

    List x;
    ...
    x.toString();
    ==>编译
    aload_1   ;将x入栈
              ;执行toString()方法
    invokeinterface java/util/List/toString()Z
    
  • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法

在编译阶段就可以确定唯一调用版本的方法有:静态方法(类名)私有方法实例构造器(<init>父类方法(super)final方法。其它统称为虚方法,在编译阶段无法确定调用版本,需要在运行期通过分派将符号引用转变为直接引用

分派

  1. 静态分派:是指在运行时对类内相同名称的方法根据描述符来确定执行版本的分派,即方法重载
  2. 动态分派:是指对于相同方法签名的方法根据实际执行对象来确定执行版本的分派。编译器是根据引用类型来判断方法是否可执行,真正执行的是实际对象方法
  3. 单分派与多分派:方法的接收者与方法的参数统称为方法的宗量。单分派是根据根据一个宗量对方法进行选择,多分派是根据多个宗量对方法进行选择
  4. 虚拟机动态分派的实现:由于动态分派是非常频繁的动作,基于性能的考虑,虚拟机中最常用的“稳定优化”手段是为类在方法区中建立一个虚方法表(Virtual Method Table,与此对应的,在invokeinterface执行时也会用到接口方法表Interface Method Table),使用虚方法表索引来代替元数据查找以提高性能。除使用方法表外,还可以使用内联缓存(Inline Cache)和基于“类型继承关系分析”(Class Hierarchy Analysis,CHA)技术的守护内联(Guarded Inlining)等
虚方法表结构

基于栈的字节码解释执行引擎

解释执行

编译过程
  • 将上面步骤独立于执行引擎,形成一个完整的编译器 ==> C/C++
  • 将其中一部分步骤实现为一个半独立的编译器 ==> Java
  • 将上面步骤和执行引擎全部集中封装在一个封闭的黑匣子中 ==> JavaScript(大多数执行器)

基于栈的指令集与基于寄存器的指令集

  • 基于栈的指令集:
    • 优点:可移植、代码相对更紧凑、编译器实现更简单等
    • 缺点:执行速度慢、完成相同功能的指令数量更多、栈位于内存中
  • 基于寄存器的指令集
    • 优点:速度快
    • 缺点:与硬件结合紧密

参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容