Java类语言(包括Java、Kotlin、Scala等)有一个非常有名的的口号——“Write Once, Run Anywhere”,这也是Java语言在近年来如此流行的原因之一。实现“一次编写,到处运行”的效果正是得益于Java虚拟机上执行的是java文件等文件编译生成的class字节码文件。
public class Test {
private int a = 1;
public int add (){
return a+1;
}
}
4.在vim乱码输入命令:%!xxd,即可转变为16进制显示。
一个字节码为32位2进制,通常用16进制查看,两位16进制数表示一个字节。如上图字节码,用8位16进制数表示左侧是偏移量,可以理解为行标识,第一行为00000000逐行递增,增量为为一个字节00000010,中间部分是字节码部分,右边该行表示的一些具体信息,常量池中的字符串方法名、类名、变量、父类信息等。
前四个字节“cafebabe” 表示魔数,用来标识这是一个字节码文件。接着第5与6个字节是“0000”是次版本号( Minor Version),在JDK12才开始启用,在此之前的版本一般都是0。第7与8个字节“0038”是表示主版本号,十进制中的56对应JDK12。
接下来是常量池部分,常量池的长度是不固定的,在第9与10个字节表示常量池的常量数量
,“0013”即十进制19,常量池有19个常量。常量池里的常量是字节码文件的资源仓库,存放字面量与符号引用。
解析常量池中的信息可以参考 常量池中17中数据类型的结构表。根据解析规则我标出了前5个常量。
使用javap -verbose Test查看类的常量池,可以看到有常量池中有18个常量,再加上0位置的索引位,共19个常量。
最后一个常量是utf8字符串“java/lang/object”,“t”是最后一个字节在ascii表中对应数字116,即16进制的74,“t“标记常量池部分结束。
访问标志中共有16个标志位可用,目前只定义了9个,没有使用到的标志位要求一律为0。
访问标志表.
在常量池后两个字节表示访问标志,用于标志类与接口层次的访问信息,0x0021是由0x0020|0x0001得到的,表示ACC _PUBLIC与ACC_SUPER两个标志为真,说明该类被public关键词修饰且允许使用invokespecial字节码指令的新语义。
接下来是类索引、父类索引、接口索引,都是两个字节,Class文件中由这三项数据来确定该类型的继承关系。类索引用于确定类的全限定名,父类索引用于确定这个类的父类的全限定名。
在访问标志后6个字节表示类与接口索引。0x0003类索引为3对应常量池中第3个常量 ,0x0004父类索引为4对应常量池第4个常量, 接口索引0x0000为0表示没有该类没有接口,若接口索引不为0,那么紧跟着的接口索引。
字段表用来描述接口或者类中声明的变量。包括类变量、实例变量表,但是不包括方法内部申明的局部变量,字段表结构了解。
解析第一个字段
0001 字段修饰符public
0005 字段名字索引,指向常量池中索引为5的“a”
0006 字段描述索引,常量池中索引为6 的“I”,表示int类型
0000 属性表计数器 0,表示无额外属性描述。
即“public int a”
Class文件存储格式中方法的描述与对字段的描述采用完全一致的方式
在字段表后边是接着的是方法表,第一个方法解析如下:
0002 方法表中有两个方法
第一个方法是:
0001 访问属性为public
0007 < init >实例构造器
0008 对应常量V
0001 包含属性 1个
0009 Code,此属性是方法字节码描述,表示是Java代码编译成的字节码。
即 “public void < init >()”
Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。属性表集合没有固定位置,直接跟在Class文件、字段表、方法表后面。
在JavaSE 12 中预定义属性有29项,详情请参见链接。
0002 表示max_stack最大操作栈数为2
0001表示max_locals表示局部变量表所需的存储空间
0000 000a 表示字节码长度为10,接下来独处10个字节码指令
2a b7 00 01 2a 04 b5 00 02 b1 是Code部分,用于描述代码
本文主要从编译一个简单的java类文件,主要将七个部分表示的含义阐述,并且将16进制字节码内容进行一一讲解。