前言JVM内存模型.png
与上图类似的JVM内存模型图见过多次,仅从概念上去理解各个区域的作用,难有深刻印象。 当学习一个类如何存储,即JVM如何解析.Class文件,能知道方法区存在的意义。本文的目的则是学习JVM如何执行一个方法,如此对栈与程序计数器有更深刻的认识。 note 文中部分内容需要.Class文件知识,但总体上不妨碍理解 字节码基础Java代码通过编译后,会将对应的函数方法转为字节码指令,如果了解.Class如何组成,可在对应方法表里的Code属性表查找到对应的一系列字节码指令。函数的执行本质上是数据运算与执行调度,因此可以用一系列的指令来进行描述。 字节码指令由一个字节长度表示,代表特定的操作含义,后面可以跟随零到多个必要参数。使用一个字节表示,意味着字节码的总数不可能操作 256条。 下面表列出了常用的数据类型对应的字节码指令,粗略看一眼就可以,需要的时候再具体查阅每条字节码的含义。
字节码用途大致分为9类,仅做简要介绍:
栈帧基础每执行调用一个方法,将用一个栈帧来支持此方法的执行。栈帧中存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等。每一个方法调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。 在进行字节码指令操作时,需要确定数据归属到什么变量,需要局部变量表;需要对数据进行操作并存取,需要操作数栈;需要知道执行到哪,需要程序计数器;可能需要在运行时转化调用的具体方法,需要动态连接。 一个线程中的方法调用链可能会很长,很多方法同时处于执行状态,对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。 栈帧结构如下图 栈帧结构图.jpg
局部变量表局部变量表用来存放方法参数和方法内部定义的局部变量,最大所需容量有max_locals表示,单位为Slot。 JVM没有指明一个 Slot占用的内存空间大小。Slot可以用32位或更小的物理内存来存放,也可以在64位虚拟机中使用64位的物理内存去实现一个Slot。对于64位的数据类型,虚拟机会以高位对齐的方式分配两个连续的Slot空间。Slot除了能存放基础数据类型外,还能存储reference和returnAddress,reference为一个对象实例的引用,能通过此引用直接或间接地查找到对象在Java堆中的数据存放的起始地址索引,以及直接或间接地查找到对象所属数据类型在方法区中的存储的类型索引。 Slot是可以重用的,如果在之后的执行区域里,局部变量如x不再使用,则x占用的Slot将会被清理再做他用。如果方法不是静态方法,一般第0个Slot为 “this”。 操作数栈字节码指令进行操作时,将从操作数栈中写入和提取内容。操作数栈中元素的数据类型必须与字节码指令的序列严格匹配。任意时刻不会超过max_stacks。 动态连接每个栈帧都包含一个指向运行时常量池中(位于方法区),该栈帧所属方法的引用,以支持方法调用过程中的动态连接。在源文件被编译成.Class文件后,.Class文件中常量池存有大量的符号引用。字节码指令进行方法调用时,会以指向方法的符号引用作为参数。这些符号引用一部分在类加载或第一次使用时转化为直接引用,称为静态解析。而另一部分将在每一次运行期间转化为直接引用,称为动态连接。 方法返回地址一个方法执行后有两种方式退出。
不管哪一种方法退出,都需要返回到方法被调用的位置,让程序继续执行。返回地址位置,可以通过程序计数器来确定,将程序计数器的值指向下一条执行,令程序继续执行。 字节码运行说了这么多,通过几个例子看字节码如何运行。 简单的运算
函数将a和b相加然后乘以2,拿到结果返回。通过命令
编译出.Class文件,能拿到具体的字节码。
上面代码转成的字节码以及字节码指令为: addAndDouble字节码指令.jpg
addAndDouble()需要的操作数栈深为stack=2,局部变量表深度为locals=2,参数args_size=2个(因为是static,不包含this),字节码指令流为 1A 1B 60 06 68 AC 。字节码命令前0、1、2等代表的是在指令在指令流中开始的位置。
过程用下图表示 相加再乘2代码字节码运行实例.png
同步方法和条件语句代码为
代码目的仅是为看同步操作和条件语句如何执行,转出的字节码指令流和字节码指令为: 条件判断和同步.jpg
syncFunction()是有两个参数的,第一个为this,第二个则为传来的a,对于操作数栈和局部变量表的操作与之前没有区别,只需注意在有this时存于局部变量表第一位。astore命令时从操作数栈取出数据存入局部变量。 ”2:“ 为代码if翻译出的字节码指令,当满足条件时从"5:"处继续执行指令,如果不满足条件则跳转到 "22:" 处。字节码指令是可以带参数的,“2:”的下一条指令从"5:"开始,if翻译出的指令占了三个字节,为 A0 00 14,其中A0代表if_icmpne指令,0x0014 为参数,十进制值为20,即要跳转的指令位置,当不满足if条件时跳转到字节码指令流第“2 + 20”处的指令,也就是“22:”处的指令。因为方法占用的最大字节为65535,因此用两位字节表示跳转位置足够。 “8:” ~ “13:” 是同步代码里的正常运行指令,简单了解即可。 异常调用代码为
转出的字节码指令流和字节码指令为: 异常方法字节码示例.jpg
如果没有异常发生,执行到“15:”的字节码指令后,会跳转到“23:”,意味着“18:” ~ "20:" 表示catch部分的执行。代码实例中,执行“11:”会发生异常,进入catch部分。 字节码执行小结以上三个例子抛砖引玉说明字节码指令的执行,其它情况可以用类似方法分析。只要弄明白栈帧里局部变量表、操作数栈、返回地址、程序计数器的作用,以及字节码指令含义和携带参数含义,就可以知道字节码是怎样执行的。比如上一张图片中, “6:”出invokespecial命令,携带一个参数,参数指向的是常量池中的一个方法描述符,通过这些信息可以知道此命令是调用File的初始化函数创建对象。 字节码指令的执行可以简述为:
方法调用上面部分说明了方法是怎样执行的,在执行之前,JVM需知道具体要调用哪个方法,可以通过解析和分派完成。 解析.Class文件 执行方法调用的字节码指令有:
能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,有 静态方法、私有方法、实例构造器、父类方法 以及被标识为 final的虚方法,没有任何手段可以覆盖或隐藏以上方法。 也因此解析是静态过程,在编译期间就可以确定,在类装载的解析阶段能把涉及到的符号引用全部转变为可以确定的直接引用。 分派分派可以分为静态分派和动态分派,可以通过“重载”和“重写”一探究竟。无论如何,目的是看虚拟机如何选择正确的目标方法。 静态分派代码例子
实际会输出 “call parent”。 对于 Parent parent = new Child() 来说,前面的Parent称为静态变量,后面的Child称为实际变量。静态变量和实际变量在程序中都可以发生一些变化,区别是静态变量的变化仅仅发生在使用时,变量本身的静态类型不会改变,在编译期可知。JVM在确定重载版本时,是通过静态变量作为依据的。 静态分派.jpg
对StaticDispatch的call()方法选取重载版本翻译成字节码指令时,选择的是Parent的版本。 动态分派动态分派则不同,需要确定运行时确定的数据类型,否则就乱了套。
执行结果输出为 “hello child”,下图为字节码解析图: 动态分派.jpg
字节码指令翻译出来的静态类型为Parent,但在方法执行是,调用的是实际类型为Child的方法。在"8: " 处,将代码新建的parent变量压入栈,然后“9: ”处调用invokevirtual指令,调用的hello()方法归属于parent(实际类型为Child),parent也称为方法的接收者(Receiver)。 invokevirtual指令的运行过程分为以下步骤:
因此,上面代码的hello()方法的实际接收者类型为Child。invokevirtual指令在运行期确定接收者的实际类型,是将常量池中的方法描述符指向了实际的直接引用上,这个过程是重写的本质。运行期根据实际类型确定方法执行版本的过程就是动态分派。 多分派与单分派分派除了能以静态分派与动态分派区分外,还能以多分派和单分派区分,两者的区别在于选取方法是,参照的“宗量”,按照一个宗量选取的称为单分派,按照多个宗量选取的称为多分派。方法的接受者、方法的参数称为方法的宗量,也可以不贴切地理解为依据。
运行结果为
多分派.jpg
在编译时期,也就是静态分派时期。选择目标方法的依据有亮点:静态类型、方法参数。因此翻译成的字节码指令invokevirtual的指令参数均指向了 Parent.choice(),一个指向的是常量符号引用是 Parent.choice(Cigarette),另一个是Parent.choice(Toy)。根据两个宗量进行原则,因此Java里的静态分派数据多分派。 在运行时期,也就是动态分派过程。执行choice()方法时,需要确定接收者的实际类型,因为要执行的方法已被确认,无需关心,因此参数的静态类型、实际类型都不会对方法的选择构成影响。只有接收者的实际类型会构成影响。因此动态分派属于单分派类型,只以一个宗量作为选择。 总结JVM 方法的执行可以总结为以下几点:
参考《深入理解 Java 虚拟机》—— 第 8 章 |
|
来自: liang1234_ > 《工作原理》