一、案例Java类结构
1.编写简单Java类上传到服务器JDK环境
我把它上传到了/data/javatest目录用于调试
public class PSVM {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
2.java命令编译PSVM类
javac PSVM.java
3.执行命令展示.class字节码
od -t xC ./PSVM.class
上面就是打印出的PSVM.class的字节码文件了。
4.字节码规范
每个JDK实现厂商都需要遵守JVM规范进行字节码的生成,规范如下:
二、.class结构解析
1.魔数、版本号、常量池元素个数(项数)
魔数cafebabe就不用提了
JDK版本16进制的34代表十进制52代表JDK8
常量池元素个数1d十进制是29,代表常量池有29项
2.常量池字节占用的完整定义
3.解析项
通过上面的定义规则,下图标记了一部分常量池项,每种连续的颜色代表一项
从第一项0a开始,CONSTANT_Methodref 0x0A 5 字节(1 + 2 + 2),所以第一行绿色5字节代表这个方法名常量,00 06代表指向第6项指向(第二行黑色框)00 0f表示索引是常量池15项(9行绿色)
4.对第1项的解析(0a 00 06 00 0f):
(1) 0a代表CONSTANT_Methodref是一个方法引用
(2) 00 06代表指向第6项指向(第二行黑色框)
第6项中07表示CONSTANT_Class常量名指向21项
第21项(最下面橙色):01 00 15 63 6f 6d 2f 73 6d 61 72 74 63 61 72 2f 6a 76 6d 2f 50 53 56 4d
01
:类型标识符,表示 CONSTANT_Utf8
00 15
:长度,表示字符串有 21 个字节
从第四字节直到最后用ascii解析就是这个类:
(3) 00 0f指向常量池第15项
第15项: 0c 00 07 00 08
0c代表CONSTANT_NameAndType (名称+类型)
00 07代表07项 01 00 06 3c 69 6e 69 74 3e ,01代表CONSTANT_Utf8 1+2+n,从第四位开始就是字符串信息:
3c 69 6e 69 74 3e正是构造方法<init>,说明这个第1项方法就是构造器
所以第一项0a 00 06 00 0f合在一起就是表示com/smartcar/jvm/PSVM<init>构造方法
其他项的向后以此类推即可
5.对第2项的解析(09 00 10 00 11):
(1) 09代表CONSTANT_Fieldref是一个成员字段引用
(2) 00 10代表指向第16项指向(第九行黄色框)07 00 17
07 00 17: 07代表CONSTANT_Class , 00 17代表第23项(最下面黄色)
第23项:01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d表示
(3)00 11代表0c 00 18 00 19(第九行红色)
00 18是第24项01 00 03 6f 75 74为out
所以第二项09 00 10 00 11合在一起就是java/lang/System out对象
6.常量池全解析
-
0a 00 06 00 0f
- 方法引用:
com/smartcar/jvm/PSVM.<init>()
(构造器)。
- 方法引用:
-
09 00 10 00 11
- 字段引用:某字段,具体解析指向的类和字段描述符。
-
08 00 12
- 字符串常量:
"Hello World"
。
- 字符串常量:
-
0a 00 13 00 14
- 方法引用:某方法,具体需解析第 19 项(类)和 20 项(名称和描述符)。
-
07 00 15
- 类引用:
com/smartcar/jvm/PSVM
(类名)。
- 类引用:
-
07 00 16
- 类引用:
java/lang/Object
(父类)。
- 类引用:
-
01 00 06 3c 69 6e 69 74 3e
- UTF-8:
<init>
(构造方法名称)。
- UTF-8:
-
01 00 03 28 29 56
- UTF-8:
()
→ 方法描述符,表示无参数、返回值为void
。
- UTF-8:
-
01 00 04 43 6f 64 65
- UTF-8:
Code
(代码段属性名称)。
- UTF-8:
-
01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65
- UTF-8:
LineNumberTable
(行号表属性)。
- UTF-8:
-
01 00 04 6d 61 69 6e
- UTF-8:
main
(主方法名称)。
- UTF-8:
-
01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56
- UTF-8:
([Ljava/lang/String;)V
→main
方法描述符。
- UTF-8:
-
01 00 0a 53 6f 75 72 63 65 46 69 6c 65
- UTF-8:
SourceFile
(源文件属性名称)。
- UTF-8:
-
01 00 09 50 53 56 4d 2e 6a 61 76 61
- UTF-8:
PSVM.java
(源文件名称)。
- UTF-8:
-
0c 00 07 00 08
- 名称和类型:
<init>()V
(构造方法的名称和描述符)。
- 名称和类型:
-
07 00 17
- 类引用:
java/io/PrintStream
。
- 类引用:
-
0c 00 18 00 19
- 名称和类型:
print(Ljava/lang/String;)V
(print
方法,参数为字符串,无返回值)。
- 名称和类型:
-
01 00 0b 48 65 6c 6c 6f 20 57 6f 72 6c 64
- UTF-8:
Hello World
(字符串内容)。
- UTF-8:
-
07 00 1a
- 类引用:
java/lang/System
。
- 类引用:
-
0c 00 1b 00 1c
- 名称和类型:
out Ljava/io/PrintStream;
(System.out
字段)。
- 名称和类型:
-
01 00 15 63 6f 6d 2f 73 6d 61 72 74 63 61 72 2f 6a 76 6d 2f 50 53 56 4d
- UTF-8:
com/smartcar/jvm/PSVM
(类全限定名)。
- UTF-8:
-
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74
- UTF-8:
java/lang/Object
(父类全限定名)。
- UTF-8:
-
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d
- UTF-8:
java/lang/System
。
- UTF-8:
-
01 00 03 6f 75 74
- UTF-8:
out
(字段名称)。
- UTF-8:
-
01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b
- UTF-8:
Ljava/io/PrintStream;
(字段类型描述符)。
- UTF-8:
-
01 00 13 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d
- UTF-8:
java/io/PrintStream
。
- UTF-8:
-
01 00 07 70 72 69 6e 74 6c 6e
- UTF-8:
println
(方法名称)。
- UTF-8:
-
01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56
- UTF-8:
(Ljava/lang/String;)V
→println
方法描述符。
- UTF-8:
7.访问修饰符
常量池之后开始就是声明信息,如访问修饰符等声明信息遵循下面的规则
访问修饰符计算规则
是个加法运算逻辑 21=0x0020+0x0001
ACC_PUBLIC 0x0001 声明为 public,可以从包外访问
ACC_SUPER 0x0020 在使用 invokespecial
指令调用父类方法时需要特殊处理
8.本类全限定名
访问修饰符下两位00 05在常量池第5项为
-
07 00 15
- 类引用:
com/smartcar/jvm/PSVM
(类名)
- 类引用:
9.父类全限定名
-
07 00 16
- 类引用:
java/lang/Object
(父类)。
- 类引用:
10.接口数
这个类没有实现接口,所以为0
11.成员变量
为了方便展示,我在java文件新加了两个成员变量用于观察成员变量