代码编译结果从本地机器码转变为字节码时存储格式发现的一小步,确实编程语言发现的一大步。
1.无关性的基石
计算机只认识0和1,我们编写的程序需要经编译器翻译成由0和1构成的二进制格式才能由计算机执行,但是随着虚拟机不的蓬勃发现,编程程序的程序编译成二进制本地机器码不再是唯一选择,越来越多的编程语言选择了与操作系统和机器指令集无关的、平台中立的格式作为程序编译后的存储格式,如字节码,代码编译结果从本地机器码转变为字节码时存储格式发现的一小步,确实编程语言发现的一大步。
Java程序一次编写,到处运行是通过运行在不同平台上的虚拟机实现的,这些虚拟机可以载入并执行同一种平台无关的字节码,各种不同平台的虚拟机与所有平台都使用同一的程序存储格式字节码,字节码时构成无关性的基石,无关性包括平台无关性和语言无关性。
平台无关性:通过字节码存储和不同平台上的虚拟机可以实现平台无关性,使java程序一次编写(编译结果是字节码),到处运行(不同平台)。
语言无关性:虚拟机和字节码存储格式也是实现语言无关性的基石,java虚拟机不和包括Java在内的任何语言绑定,它只与字节码这种特定的存储格式关联,任何功能型语言都可以表示为能被java虚拟机所接收的字节码文件,如下图所示:语言无关性
字节码所能提供语义肯定会比Java语言本身更强大,因此java语言本身无法有效支持的语言特性并不代表字节码本身无法有效支持,这也为其它语言实现一些有别于java的语言特性提供了基础。
2.Class类文件的结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有添加任何的分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据。当遇到占用8位字节以上空间的数据项时,则会按照高位在前的方式分割为若干8位字节进行存储。
Class文件格式采用类似于C语言结构体的伪结构来存储数据,伪结构只有两种数据类型:无符号数和表。
无符号数属于基础数据类型,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节。可以用来描述数字、索引引用,数量值或者按照UTF-8编码构成字符串值。
表是由多个无符号数或者其他表座位数据项构成的复合数据类型,所有表都习惯性以"_info”结尾。表用于描述有层次关系的复合结构的数据。
3.解析字节码
ClassTest.java类源码如下:
package test;
public class ClassTest {
int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
我们可以使用WinHex来查看ClassTest.class文件,如下图所示:
ClassTest字节码.JPG
上图是编译好的字节码文件,我们可以看到一堆16进制的字节。如果你使用IDE去打开,也许看到的是已经被反编译的我们所熟悉的java代码,而这才是纯正的字节码。
Oracle也为我们准备了专门用于分析Class文件字节码的工具:javap
javap -verbose ClassTest.class 结果如下所示:
Classfile /C:......./test/ClassTest.class
Last modified 2019-11-6; size 461 bytes
MD5 checksum e1ab06fd01a5ab9eada158c93cb5cb31
Compiled from "ClassTest.java"
public class test.ClassTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // test/ClassTest.a:I
#3 = Class #22 // test/ClassTest
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ltest/ClassTest;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 ClassTest.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 test/ClassTest
#23 = Utf8 java/lang/Object
{
int a;
descriptor: I
flags:
public test.ClassTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 3: 0
line 4: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Ltest/ClassTest;
public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ltest/ClassTest;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 11: 0
line 12: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Ltest/ClassTest;
0 6 1 a I
}
SourceFile: "ClassTest.java"
字节码总览图如下所示:
字节码文件中具体包含的内容有:
魔数:1-4字节,用来确定这个文件是否JVM认可的Class文件,它的值位:0xCAFEBABE。
版本号:5-6字节标识次版本号(minor version),7-8字节表示主版本号(major version)。
常量池:常量池中常量的数量不是固定的,主要存放字面量(文本字符串、final常量)和符号引用(类和接口全限定名、字段名称与描述、方法名称与发描述)。
访问标志:主要用来识别类和接口的访问信息(是否为public、是否为final、是否为接口、是否为抽象、是否为注解、是否为枚举等)。
类、父类、接口索引:用来确定该类和父类的全限定名,以及描述该类实现了哪些接口。
字段表集合:描述接口或者类中声明的变量、字段。包括类变量和实例变量,不包括方法内的局部变量。
方法表集合:用来描述方法相关信息
字节码中对各部分的具体解读相对枯燥,如果想比较深入了解虚拟机,可以对以下参考资料进行学习。
Java虚拟机原理图解
一文让你明白Java字节码
《深入理解JVM》
4.本文参考资料
Java虚拟机原理图解
一文让你明白Java字节码
Java类加载机制
《深入理解Java虚拟机》
网友评论