走进JVM
Java源代码是怎么被机器识别并执行的呢?答案是Java虚拟机,即Java Virtual Machine,简称JVM。
JVM提供商包括Sun、BEA、IBM等。
1999年,Sun公司发布了C/C++实现的HotSpot Java虚拟机。2006年,在JavaOne大会上开源了其相关核心技术,启动OpenJDK项目,逐步形成了活跃的OpenJDK社区。
2010年,Sun公司被Oracle公司收购。Oracle的HotSpot JVM实现,是目前OpenJDK使用的主流JVM,它采用解释与编译混合执行的模式,其JIT技术采用分层编译,极大地提升了Java的执行速度;
BEA的JRockit在2008年被并入HotSpot;
IBM的J9也在2017年开源,形成了现在的OpenJ9社区。
一、字节码
0与1是计算机仅能识别的信号,经过0与1的不同组合产生了数字之上的操作。另外通过不同的组合亦产生了各种字符。同样可以通过不同的组合产生不同的机器指令。在不同的时代,不同的厂商,机器指令组成的集合是不同的。毕竟CPU是底层基础硬件,指令集通常以扩展兼容的方式向前不断演进。而机器码是离CPU指令集最近的编码,是CPU可以直接解读的指令,因此机器码肯定是与底层硬件系统耦合的。
Java的使命就是一次编写、到处执行。在不同的操作系统、不同硬件平台上,均可以不用修改代码即可顺畅执行。
如何实现跨平台?
中间码,即"字节码"(Bytecode)。Java所有的指令有200个左右,一个字节(8位)可以存储256种不同的指令信息,一个这样的字节称为字节码(Bytecode)。
在代码执行过程中,JVM将字节码解释执行,屏蔽对底层操作系统的依赖;JVM也可以将字节码编译执行,如果是热点代码,会通过JIT动态地编译为机器码,提高执行效率。
图4.1所示,十六进制表示的二进制流通常是一个操作指令。
起始的4个字节非常特殊,即绿色框的cafe base 是 Gosling 定义的一个魔法数,意思是Coffee Baby,其 十进制的值为 3405691582。它的作用是:标志该文件是一个Java类文件,如果没有识别到该标志,说明该文件不是Java 类文件或者文件已受损,无法进行加载。
而红色框代表当前版本号,0x37的十进制为55,是JDK11的内部版本号。
其他JDK版本:
cafe babe 0000 0032 十进制50 JDK1.6
cafe babe 0000 0033 十进制51 JDK1.7
cafe babe 0000 0034 十进制52 JDK1.8
字节码的主要指令:
- 加载或存储命令
在某个栈帧中,通过指令操作数据在虚拟机栈的局部变量表与操作栈之间来回传输,常见指令如下:
1)将局部变量加载到操作栈中。
如:ILOAD 将int 类型的局部变量压入栈
ALOAD 将对象引用的局部变量压入栈等
2)从操作栈顶存储到局部变量表
如:ISTORE、ASTORE等
3)将常量加载到操作栈顶,这是极为高频使用的指令。
如:ICONST加载的是-1~5的数
BIPUSH即Byte Immediate PUSH,加载 -128~127之间的数
SIPUSH即Short Immediate PUSH ,加载-32768~32767之间的数
LDC 即Load Constant,在 -2147483648 ~ 2147483647 或者是字符串时,JVM采用LDC指令压入占中等
例子:
package easyCode.chapterfour.bytecode;
public class ByteCodeDemo {
public static void main(String[] args) {
int a = -2;
int b = -1;
int e = 20000;
int f = 40000;
}
}
javap -c ByteCodeDemo.class
-
运算指令
对两个操作栈帧上的值进行运算,并把结果写入操作栈顶。
如IADD、IMUL等 -
类型转换指令
显示转换两种不同的数值类型。如I2L、D2F等。 -
对象创建与访问指令
根据类进行对象的创建、初始化、方法调用相关指令,常见指令如下:
1)创建对象指令。如NEW、NEWARRAY等
2)访问属性指令。如GETFIELD、PUTFIFELD、GETSTATIC等
3)检查实例类型指令。如INSTANCEOF、CHECKCAST等 -
操作栈管理指令
JVM提供了直接控制操作栈的指令,常见如下:
1)出栈操作。
2)复制栈顶元素并压入栈。如POP即一个元素,POP2即2个元素。 -
方法调用与返回指令
常见指令如下:
1) invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
2)invokespecial 指令用于调用实例初始化(<init>)方法、私有方法和父类方法等。
3)invokestatic 调用类静态方法(static方法)。
4)return 指令返回void类型。 -
同步指令
JVM使用方法结构中ACC_SYNCHRONIZED标志同步方法,指令集中有monitorenter和monitorexit支持sychronized语义。
未完待续。。。。
参考:码出高效:Java手册
http://openjdk.java.net/
https://github.com/eclipse/openj9
http://ifeve.com/javacode2bytecode/