《Java虚拟机原理图解》4.JVM机器指令集

0. 前言

Java虚拟机和真实的计算机一样,执行的都是二进制的机器码;而我们将.java 源码编译成.class 文件,class文件便是Java虚拟机可以认识的二进制机器码,Java可以识别class文件里的信息和机器指令,进而执行这些机器指令。

那么,Java虚拟机是怎样执行这些二进制的机器码的呢?

本文将通过一个很easy的样例,带你感受一下Java虚拟机执行机器码的过程和其工作的基本原理。

读完本文,你将会了解到:

1、Java虚拟机对执行时虚拟机栈(JVM Stack) 的组织

2、方法调用过程是如何在JVM中表示的

3、JVM对一个方法运行的基本策略

4. JVM机器指令的格式

5. 机器指令的运行模式---基于操作数栈的模式

1. Java虚拟机对执行时虚拟机栈(JVM Stack)的组织

Java虚拟机在执行时会为每个线程在内存中分配了一个虚拟机栈,来表示线程的执行状态和信息。虚拟机栈中的元素称之为栈帧(JVM stack frame),每个栈帧表示这对一个方法的调用信息。例如以下所看到的:

上述的描写叙述可能会有点抽象。为了给读者一个直观的感受。我们定义一个简单的Java类,然后执行这个执行这个类。逐步分析整个Java虚拟机的执行时信息的组织的。

2.  方法调用过程在JVM中是怎样表示的

我们将定义例如以下带有main方法的简单类org.louis.jvm.codeset.Bootstrap.java ,逐步分析该类在JVM中是怎样表示的。方法是怎样一步步执行的:

package org.louis.jvm.codeset;
/**
 * JVM 原理简单用例
 * @author louis
 *
 */
public class Bootstrap {

	public static void main(String[] args) {
		String name = "Louis";
		greeting(name);
	}

	public static void greeting(String name)
	{
		System.out.println("Hello,"+name);
	}

}

当我们将Bootstrap.java 编译成Bootstrap.class 并执行这段程序的时候,在JVM复杂的执行逻辑中,会有下面几步:

1. 首先JVM会先将这个Bootstrap.class 信息载入到 内存中的方法区(Method Area)中。

Bootstrap.class 中包括了常量池信息,方法的定义 以及编译后的方法实现的二进制形式的机器指令。全部的线程共享一个方法区,从中读取方法定义和方法的指令集。

2. 接着。JVM会在Heap堆上为Bootstrap.class 创建一个Class<Bootstrap>实例用来表示Bootstrap.class 的 类实例。

3. JVM開始运行main方法。这时会为main方法创建一个栈帧。以表示main方法的整个运行过程(我会在后面章节中具体展开这个过程)。

4. main方法在运行的过程之中,调用了greeting静态方法,则JVM会为greeting方法创建一个栈帧,推到虚拟机栈顶(我会在后面章节中具体展开这个过程)。

5.当greeting方法执行完毕后,则greeting方法出栈。main方法继续执行;

JVM方法调用的过程是通过栈帧来实现的,那么。方法的指令是怎样执行的呢?弄清楚这个之前。我们要先了解对于JVM而言,方法的结构是什么样的。

我们知道。class 文件时 JVM可以识别的二进制文件,当中通过特定的结构描写叙述了每一个方法的定义。

JVM在编译Bootstrap.java 的过程中。在将源码编译成二进制机器码的同一时候,会推断当中的每个方法的三个信息:

1 ).  在执行时会使用到的局部变量的数量(作用是:当JVM为方法创建栈帧的时候。在栈帧中为该方法创建一个局部变量表。来存储方法指令在运算时的局部变量值)

2 ).  其机器指令运行时所须要的最大的操作数栈的大小(当JVM为方法创建栈帧的时候。在栈帧中为方法创建一个操作数栈。保证方法内指令能够完毕工作)

3 ).  方法的參数的数量

经过编译之后。我们能够得到main方法和greeting方法的信息例如以下:

注: 上述编译后的信息所有都存储在Bootstrap.class 文件里,并依照这Class文件格式的形式存储,关于Class文件格式的定义。我在前几篇文章中已经做了很详尽的介绍,假设您所有阅读了。那么相信您已经能够“读懂” class 文件了。怎样读懂class二进制文件里关于method及其对应机器码的组织。请阅读《Java虚拟机原理图解》1.5、
class文件里的方法表集合--method方法在class文件里是如何组织的

JVM执行main方法的过程:

1.为main方法创建栈帧: 

JVM解析main方法,发现其 局部变量的数量为 2。操作数栈的数量为1, 则会为main方法创建一个栈帧(VM Stack),并将其增加虚拟机栈中:

2. 完毕栈帧初始化:

main栈帧创建完毕后,会将栈帧push 到虚拟机栈中,如今有两步重要的事情要做:

a). 计算PC值。

PC 是指令计数器。其内部的值决定了JVM虚拟机下一步应该运行哪一个机器指令。而机器指令存放在方法区。我们须要让PC的值指向方法区的main方法上;

初始化 PC = main方法在方法区指令的地址+0。

b). 局部变量的初始化。main方法有个入參(String[] args) ,JVM已经在main所在的栈帧的局部变量表中为其空出来了一个slot 。我们须要将 args 的引用值初始化到局部点亮表中;

    1. 接着JVM開始读取PC指向的机器指令。

      如上图所看到的。main方法的指令序列:12 10 4c 2b b8 20 12 b1 ,通过JVM虚拟机指令集规范。能够将这个指令序列解析成下面Java汇编语言:

机器指令 汇编语言 解释 对栈帧的影响
0x12 0x10 ldc #16 将常量池中第16个常量池项引用推到操作数栈栈顶。

常量池第16项是CONSTANT_UTF-8_INFO项。表示”Louis”字符串

0x4c astore_1 操作数栈的栈顶元素出栈,将栈顶元素的值赋给index=1 的局部变量表元素上。

这里等价于:name = “Louis”.

0x2b aload_1 将局部变量表中index=1的元素的值推到操作数栈栈顶
0xb8 0x20 0x12 invokestatic #18 0xb8表示机器指令invokestatic,操作数是0x20 << 8| 0x12 = 18。操作数18表示指向常量池第18项,该项是main方法的符号引用:

org/louis/jvm/codeset/Bootstrap.greeting:(Ljava/lang/String;)V

当JVM运行这条语句的时候,会做下面几件事:

a).方法符号引用校验。

会校验这种方法的符号引用。依照这个符号规则 在常量池中查找是否有这种方法的定义,假设找到了此方法的定义,则表示解析成功。假设是方法greeting:(Ljava/lang/String;)V没有找到,JVM会抛出错误NoSuchMethodError

b).为新的方法调用创建新的栈帧。然后JVM会为此方法greeting创建一个新的栈帧(VM stack),并依据greeting中操作数栈的大小和局部变量的数量分别创建对应大小的操作数栈;然后将此栈帧推到虚拟机栈的栈顶。

c).更新PC指令计数器的值。

将当前PC程序计数器的值记录到greeting栈帧中。当greeting运行完毕后,以便恢复PC值。更新PC的值,使下一条运行的指令地址指向greeting方法的指令開始部分。

这条语句会使当前的main方法运行暂停,使JVM进入对greeting方法的运行其中当greeting方法运行完毕后,才会恢复PC程序计数器的值指向当前下一条指令。

 
0xb1 return 返回  
       

当main方法调用greeting()时。 JVM会为greeting方法创建一个栈帧,用以表示对greeting方法的调用。详细栈帧信息例如以下:

详细的greeting方法的机器码表示的含义例如以下图所看到的:

机器指令 汇编语言 解释 常量池引用
b2 20 1a getstatic     #26 获取指定类的静态域,并将其值压入栈顶.

将常量池中的第26个符号引用推到操作数栈中:

#26:

// Field java/lang/System.out:Ljava/io/PrintStream;

bb 20 20 new           #32 创建一个对象。并将其引用值压入栈顶。

创建一个java/lang/StringBuider实例,将其压入栈顶。

#32:

// class java/lang/StringBuilder

59  dup 复制操作数栈栈顶的值,并插入到栈顶  
12 22 ldc           #34 从执行时常量池中提取数据推入操作数栈

将“Hello” String引用拷贝到 操作数栈中

#34:

// String Hello,

b7 20 24   invokespecial #36  调用超类构造方法,实例初始化方法,私有方法。

此处调用StringBuilder(String)构造方法。并将结果推到栈顶

#36:

// Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V

2a   aload_0 将第一个局部变量的引用推到栈顶。

当前局部变量表的第一个局部变量引用是 :“Louis”,即将Louis推到栈顶

 
b6 20 26 invokevirtual #38 调用超类构造方法。实例初始化方法,私有方法。

StringBuilder实例的 append(String ) 方法。表示:

"Hello,"+"Louis".

// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
b6 20 2a   invokevirtual #42 调用超类构造方法,实例初始化方法,私有方法。

调用StringBuilder实例的toString()方法,结果保留在栈顶。

 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
b6 20 2e invokevirtual #46 调用超类构造方法,实例初始化方法。私有方法。

调用System.out.println(String)方法

 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
b1 return 结束返回  
       

3.  JVM对一个方法运行的基本策略

一般地,对于java方法的执行,在JVM在其某一特定线程的虚拟机栈(JVM Stack) 中会为方法分配一个 局部变量表。一个操作数栈,用以存储方法的执行过程中的中间值存储。

因为JVM的指令是基于栈的,即大部分的指令的运行。都伴随着操作数的出栈和入栈。所以在学习JVM的机器指令的时候。一定要铭记一点:

每一个机器指令的运行。对操作数栈和局部变量的影响,充分地了解了这个机制,你就能够很顺畅地读懂class文件里的二进制机器指令了。

例如以下是栈帧信息的简化图,在分析JVM指令时,脑海中对栈帧有个清晰的认识:

4.  机器指令的格式

所谓的机器指令,就是仅仅有机器才可以认识的二进制代码。一个机器指令分为两部分组成:

注:

a).  如上图所看到的JVM虚拟机的操作码是由一个字节组成的,也就是说对于JVM虚拟机而言,其指令的数量最多为 2^8,即 256个;

b). 上图中的操作码如:b2,bb,59....等等都是表示某一特定的机器指令,为了方便我们识别,其分别有对应的助记符:getstatic,new,dup.... 这样方便我们理解。

5.  机器指令的运行模式---基于操作数栈的模式

对于传统的物理机而言。大部分的机器指令的设计都是寄存器的。物理机内设置若干个寄存器,用以存储机器指令执行过程中的值。寄存器的数量和支持的指令的个数决定了这个机器的处理能力。

可是Java虚拟机的设计的机制并非这种,Java虚拟机使用操作数栈 来存储机器指令的运算过程中的值。全部的操作数的操作,都要遵循出栈和入栈的规则。所以在《Java虚拟机规范》中,你会发现有非常多机器指令都是关于出栈入栈的操作。

本文旨在介绍JVM虚拟机指令的执行原理,假设你想更深入地了解指令集的信息以及使用注意事项,请您阅读《Java虚拟机规范(Java Virtual Machine Specification)》 关于机器指令集的具体定义。

Java_Virtual_Machine_Specification_Java_SE_7_中文版

时间: 2024-10-14 21:30:11

《Java虚拟机原理图解》4.JVM机器指令集的相关文章

IT忍者神龟之 《Java虚拟机原理图解》JVM运行时数据区

[last updated :2014/11/7]      JVM运行时数据区(JVM Runtime Area)其实就是指JVM在运行期间,其对计算机内存空间的划分和分配.本文将通过以下几个话题来讨论JVM运行时数据区. Topic 1. JVM运行时数据区里有什么? Topic 2. 虚拟机栈 是什么?虚拟机栈里有什么? Topic 3.栈帧是什么?栈帧里有什么? Topic 4. 方法区是什么?方法区里有什么? Topic 1.JVM运行时数据区里有什么? Topic 2. 虚拟机栈是什

《Java虚拟机原理图解》3、JVM执行时数据区

[last updated :2014/11/7]     JVM执行时数据区(JVM Runtime Area)事实上就是指JVM在执行期间,其对计算机内存空间的划分和分配.本文将通过下面几个话题来讨论JVM执行时数据区. Topic 1. JVM执行时数据区里有什么? Topic 2. 虚拟机栈 是什么?虚拟机栈里有什么? Topic 3.栈帧是什么?栈帧里有什么? Topic 4. 方法区是什么?方法区里有什么? Topic 1.JVM执行时数据区里有什么? Topic 2. 虚拟机栈是什

Java虚拟机原理图解----JVM运行时数据区

     JVM运行时数据区(JVM Runtime Area) 其实就是指JVM在运行期间,其对计算机内存空间的划分和分配.本文将通过以下几个话题来讨论JVM运行时数据区. Topic 1. JVM运行时数据区 里有什么? Topic 2. 虚拟机栈 是什么?虚拟机栈 里有什么? Topic 3. 栈帧是什么?栈帧 里有什么? Topic 4. 方法区 是什么?方法区 里有什么? Topic 1.JVM运行时数据区里有什么? Topic 2. 虚拟机栈是什么?虚拟机栈里有什么? Topic 3

《Java虚拟机原理图解》1.5、 class文件中的方法表集合--method方法在class文件中是怎样组织的

0. 前言 了解JVM虚拟机原理是每一个Java程序员修炼的必经之路.但是由于JVM虚拟机中有很多的东西讲述的比较宽泛,在当前接触到的关于JVM虚拟机原理的教程或者博客中,绝大部分都是充斥的文字性的描述,很难给人以形象化的认知,看完之后感觉还是稀里糊涂的. 感于以上的种种,我打算把我在学习JVM虚拟机的过程中学到的东西,结合自己的理解,总结成<Java虚拟机原理图解> 这个系列,以图解的形式,将抽象的JVM虚拟机的知识具体化,希望能够对想了解Java虚拟机原理的的Java程序员 提供点帮助.

《Java虚拟机原理图解》6、 class文件中的方法表集合--method方法在class文件中是怎样组织的

0. 前言 了解JVM虚拟机原理是每一个Java程序员修炼的必经之路.但是由于JVM虚拟机中有很多的东西讲述的比较宽泛,在当前接触到的关于JVM虚拟机原理的教程或者博客中,绝大部分都是充斥的文字性的描述,很难给人以形象化的认知,看完之后感觉还是稀里糊涂的. 感于以上的种种,我打算把我在学习JVM虚拟机的过程中学到的东西,结合自己的理解,总结成<Java虚拟机原理图解> 这个系列,以图解的形式,将抽象的JVM虚拟机的知识具体化,希望能够对想了解Java虚拟机原理的的Java程序员 提供点帮助.

《Java虚拟机原理图解》1.4 class文件中的字段表集合--field字段在class文件中是怎样组织的

0.前言 了解JVM虚拟机原理是每一个Java程序员修炼的必经之路.但是由于JVM虚拟机中有很多的东西讲述的比较宽泛,在当前接触到的关于JVM虚拟机原理的教程或者博客中,绝大部分都是充斥的文字性的描述,很难给人以形象化的认知,看完之后感觉还是稀里糊涂的. 感于以上的种种,我打算把我在学习JVM虚拟机的过程中学到的东西,结合自己的理解,总结成<Java虚拟机原理图解> 这个系列,以图解的形式,将抽象的JVM虚拟机的知识具体化,希望能够对想了解Java虚拟机原理的的Java程序员 提供点帮助. 读

Java虚拟机原理图解-- 1.2.2、Class文件中的常量池详解(上)[转]

NO1.常量池在class文件的什么位置? 我的上一篇文章<Java虚拟机原理图解> 1.class文件基本组织结构中已经提到了class的文件结构,在class文件中的魔数.副版本号.主版本之后,紧接着就是常量池的数据区域了,如下图用红线包括的位置: 知道了常量池的位置后,然后让我们来揭秘常量池里究竟有什么东西吧- NO2.常量池的里面是怎么组织的? 常量池的组织很简单,前端的两个字节占有的位置叫做常量池计数器(constant_pool_count),它记录着常量池的组成元素  常量池项(

Java虚拟机原理图解-- 1.1、class文件基本组织结构 [转]

作为Java程序猿,我们知道,我们写好的.java 源代码,最后会被Java编译器编译成后缀为.class的文件,该类型的文件是由字节组成的文件,又叫字节码文件.那么,class字节码文件里面到底是有什么呢?它又是怎样组织的呢?让我们先来大概了解一下他的组成结构吧. NO1. 魔数(magic) 所有的由Java编译器编译而成的class文件的前8个字节都是“0xCAFEBABE”        它的作用在于:当JVM在尝试加载某个文件到内存中来的时候,会首先判断此class文件有没有JVM认为

Java虚拟机原理图解-- 1.2、class文件中的常量池

了解JVM虚拟机原理 是每一个Java程序员修炼的必经之路.但是由于JVM虚拟机中有很多的东西讲述的比较宽泛,在当前接触到的关于JVM虚拟机原理的教程或者博客中,绝大部分都是充斥的文字性的描述,很难给人以形象化的认知,看完之后感觉还是稀里糊涂的.感于以上的种种,我打算把我在学习JVM虚拟机的过程中学到的东西,结合自己的理解,总结成<Java虚拟机原理图解> 这个系列,以图解的形式,将抽象的JVM虚拟机的知识具体化,希望能够对想了解Java虚拟机原理的的Java程序员 提供点帮助. 上一章节&l