public class MyTest1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
执行
javac MyTest1.java
javap -verbose MyTest1.class
得到对应字节码
Last modified 2020-9-13; size 375 bytes
MD5 checksum 8ba81fc79a80d987e02b14067a155703
Compiled from "MyTest1.java"
public class com.example.demo.com.jvm.bytecode.MyTest1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#17 // java/lang/Object."":()V
#2 = Fieldref #3.#18 //com/example/demo/com/jvm/bytecode/MyTest1.a:I
#3 = Class #19 // com/example/demo/com/jvm/bytecode/MyTest1
#4 = Class #20 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 getA
#12 = Utf8 ()I
#13 = Utf8 setA
#14 = Utf8 (I)V
#15 = Utf8 SourceFile
#16 = Utf8 MyTest1.java
#17 = NameAndType #7:#8 // "":()V
#18 = NameAndType #5:#6 // a:I
#19 = Utf8 com/example/demo/com/jvm/bytecode/MyTest1
#20 = Utf8 java/lang/Object
{
public com.example.demo.com.jvm.bytecode.MyTest1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 3: 0
line 5: 4
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 8: 0
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 12: 0
line 13: 5
}
SourceFile: "MyTest1.java"
使用javap -verbose命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息,类变量与成员变量信息。
魔数:所有的.class字节码文件的前四个字节都是魔数,魔数值为固定值:0xCAFFBABE【咖啡宝贝】(在解析时会查找固定魔数当不符合规范就不进行解析了)
3.魔数之后的4个字节为版本信息,前两个字节表示minnor version(次版本号),后两个字节表示major version(主版本号)。这几的版本号为00 00 00 34,换算成十进制,表示次版本号为0,主版本号为52.所以改文件的版本号为:1.8.0 可以通过java -version命令来验证这一点。
常量池(constant pool):紧接着主版本号之后的就是常量池入口。一个java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说java类中定义的方法与变量信息,都是存储在常量池当中。常量池中主要存储两类常量:字面量和符号引用。字面量如文本字符串,java声明的final的常量值等,而符号引用如类和接口的全局限定名。字段的名称和描述符符,方法的名称和描述符等。
常量池的总体结构:Java类所对应的常量池主要由常量池数量和常量池数组(常量表)这两部分共同构成。常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟在常量池数量之后,常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型,结构都是不同的,长度当然也就不同;但是每一种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数 = 常量池Count - 1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定请款项需要表达【不引用任何一个常量池】的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它位于常量表中,这个常量就对应null值;所以,常量池的索引从1而非0开始。
在JVM规范中,每个变量/字段都有描述信息,描述信息主要的作用是描述字段的数据类型。方法的参数列表(包括数量,类型和顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写的字符来表示,对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,JVM都只使用一个大写的字母来表示,如下所示:B - byte,C - char, D - double, F- float,I - int, J - lang, S - short,Z - boolean, V - void, L- 对象类型。如Ljava/lang/String; (分号不要忘记)
对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为[I,String[][]被记录为[[Ljava/lang/String;
用描述符描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法: String getRealnamebyIdAndNickname(int id,String name)的描述符为: (I,Ljava/lang/String;)Ljava/lang/String;
0xCAFFBABE【咖啡宝贝】(在解析时会查找固定魔数当不符合规范就不进行解析了)
分为两部分版本后两个字节是对应的常量count 第二部分是常量表(资源仓库)为count-1个常量
编译之后的字节码文件
字节码常量池对应的第一个参数 0A(固定都是第一个字节)对应的是10如图
此时后续4(2+2)个字节都是描述 对应的index
4 = 416^0 指向如图下 #4 = Class
20 = 116^1 +4*16 #20 = NameAndType
而 #4 指向 #23 java/lang/Object
#20 指向 #7#8 分别为 对应构造方法, ()V 对应构造方法无参无返回
组合就是java/lang/Object."":()V 对应的就是
第二个常量池信息如下图
对应的 com/cx/jvm/bytecode/Mytest1.a:I 对应的意思是: myTest1类 参数 int类型a属性
对应表格表示的是 为长度为1个字节的字符串 61 对应的 2进制为 97 97是a的ASCII码值 其他如下(我们可以在线转ASCII值)
如01 00 06 3c 69 6e 69 74 3e
所以 jvm通过压缩将其class文件 字节码所有字节通过jvm规范通过ASCII都能转化成对应的java文件 可以看出方法构造方法和属性 父类信息都是存储在常量池的
jvm在加载的时候已经知道到需要解析的常量池对应的常量数据(都是通过他们常量的标识符来确定的) 如上 当解析到23个时就解析完成。
对应类的访问修饰符对应如下几种
ACC_PRIVATE 0X0002 表示private修饰符
当存在多种类修饰符 相当于对应类型值累加
如
对应的就是 20+1 = 21
此时会去常量池(资源仓库)去找#3对应的数据 当前例子#3指向#19对应的就是com/example/demo/com/jvm/bytecode/MyTest1完全的限定名
#19 = Utf8 com/example/demo/com/jvm/bytecode/MyTest1
#4 = Class #20 // java/lang/Object 都是去常量池中找对应的数据
接口分为两部分 接口数量(super class后两个字节)以及接口表
当接口count为0 接口表就不会出现
变量分为两部分变量数量(interface后两个字节 只统计类中静态变量和成员变量)以及变量表
变量表也有自己的结构的
access_flags 需要再修饰符中找对应的数据
此时0006 后面的两个字节是0000无特殊的attributes(如上图字段结构所示) count 就忽略不展示
就可以得出成员变量为 private int a;
方法也分为两种 count (两个字节)和方法表
此离有三个方法 一个无参构造方法和对应get set方法