前面写了几篇关于Java虚拟机和字节码的文章,由于没有进行系统的整理,导致在阅读的过程中很难有一个清晰的框架和思路,今天就在回顾的基础上以及加上新的理解,统一整理如下。
一、Java虚拟机的概念
Java虚拟机(Java Virtual Machine,JVM),一种能够运行Java字节码的虚拟机。作为一种编程语言的虚拟机,实际上不只是专用于Java语言,只要生成的编译文件匹配JVM对加载编译文件格式要求,任何语言都可以由JVM编译运行,比如Kotlin、Groovy等。
在这里可以看出Java虚拟机提供了两个很重要的特性:平台无关性、语言无关性。
平台无关性是指:Java字节码是运行在Java虚拟机上而不是具体的操作系统,然后虚拟机运行在操作系统上。这样的好处就是在编写程序时无需考虑各种系统的差异,只要编译后的字节码遵循Java虚拟机的规范,就可以运行在各个系统上。
语言无关性是指:Java虚拟机直接运行的是字节码文件,但字节码是用什么语言编译而来Java虚拟机不在乎,可以是Java语言、Groovy语言等。
二、Java虚拟机的基本结构
JVM由三个主要的子系统构成:类加载子系统、运行时数据区(内存结构)、执行引擎,如下图:
下面分别对这三个部分进行讲解
1、类加载子系统
该篇文章已经做过详细的介绍:虚拟机类加载机制
2、运行时数据区(内存结构)
该篇文章已经做过详细的介绍:Java虚拟机内存划分
3、执行引擎
“虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行的能力,其区别是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面的,而虚拟机的执行引擎是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。
下面通过一个简单的实例来看执行引擎是如何执行程序的
JvmDemo.java源码:
package cn.yxz;
public class JvmDemo {
public int math() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
JvmDemo demo = new JvmDemo();
demo.math();
}
}
通过javap -c JvmDemo.class反编译后的内容如下:
在分析这段指令前,首先介绍下面要用到的几个概念:
1、operand stack:操作数栈,是一个栈结构,只做临时存储作用。它位于local variable(局部变量表)和CPU中间,存到局部变量表里的值必须从操作数栈获取,CPU需要运算的值也从里面获取,并且这个获取操作是出栈操作,即操作后该值不再存在在栈中。
2、local variable:局部变量表。局部变量表是一种数组结构,单位是32位即4个字节,像64位的long和double类型需要占两个单位。
下面主要分析一下math方法,命令参考链接:
1、iconst_1:push int 1 constant onto the operand stack,将1推送到操作数栈中
2、istore_1:store int from operand stack into local variable at index 1,将操作数栈中的值出栈并存放在局部变量表的下标为1的位置
3、iconst_2:push int 2 constant onto the operand stack,同1,将2推送到操作数栈中
4、istore_2:store int from operand stack into local variable at index 2,同2,将操作数栈中的值出栈并存放在局部变量表的下标为2的位置
5、iload_1:load int from local variable at index 1 and push onto operand stack ,与istore_1对应,将局部变量表中下标为1的值复制一份存入到操作数栈中,注意:这时是复制,因为后面可能还需要用到
6、iload_2:load int from local variable at index 2 and push onto operand stack ,同5
7、iadd:pop the first 2 ints from the operand stack, add them and place the int result onto the operand stack,将最上面的两个值出栈并送去CPU执行加法,然后将值存到操作数栈
8、bipush:convert index byte (signed 8-bit integer) to an int and push it onto the operand stack,将索引字节(带符号的8位整数)转换为int并将其推送到操作数堆栈上
9、imul:pop the first 2 ints from the operand stack, multiply them and place the integer result onto the operand stack,将最上面的两个值出栈并送去CPU执行乘法,然后将值存到操作数栈
10、istore_3,同2
11、iload_3,同5
12、ireturn:pop int from operand stack of the current frame (= method) and push onto the operand stack of the frame of the invoker,将当前栈帧的栈顶的值出栈并推送到调用者的栈真的操作数栈