类文件结构
package com.szu.jvm.learn01_bytecode;
/*
* @Author 郭学胤
* @University 深圳大学
* @Description
* @Date 2021/2/20 13:33
*/
public class L01_EmptyClass {
}
下图为一个空文件编译出来的class文件
1. 魔数和version
-
前四个字节:
CA FE BA BE
,类文件固定开头,名字叫做魔数(magic number) -
第五六七八字节:
00 00 00 3B
,代表版本信息, 其中前两个字节为minor version,后两个字节为major versionJDK15编译出来为
3B
, JDK11编译出来为37
2. 常量池(表格在最后)
往后开始常量池部分,这是类文件中最常的部分之一。常量池中的部分基本都是互相引用,所以查看起来比较复杂。
常量池最开始的两个字节代表常量池中常量数量,十六进制0x10 = 16
,常量的数量为16 - 1 = 15项
第一项
(查表可知,表在最后)
0A
代表 Method_ref_info,后边是两个两个字节的数:00 03 00 0D
所以00 03
指向的常量池中的第三项,所以第三项就是这个方法的类型描述符
00 0D
指向的是常量池中的第十三项,第十三项就是这个方法的名字和返回值类型
第二项
07
:代表 Class_info
00 0E
指向常量池的第14项,意思就是第14项存储的字符串就是这个类的全限定名
第三项
07
:代表 Class_info
00 0F
指向常量池的第15项,意思就是第15项存储的字符串就是这个类的全限定名
常量池的第一项的第一个参数指向第三项,第三项又指向第十五项,所以第十五项是什么,第一项那个方法的类型描述符是什么了。
直接看第十四和十五项,
查表可知这两项都是字符串
红框01
代表后边是 UTF8
字符串,后边的绿框代表这个字符串的长度,00 2B = 43
, 00 10 = 16
第14项为第一个字符串:com/szu/jvm/learn01_bytecode/L01_EmptyClass
- 因为第二项指向第14项,所以此类全限定名为
com/szu/jvm/learn01_bytecode/L01_EmptyClass
第15项为第二个字符串为java/lang/Object
- 此类第一项那个方法的类型为
java/lang/Object
类型,虽然是一个空类,但是每个类都有自己的默认构造方法,然而每个类都是Object的子类,所以会有这种现象存在。
3. 访问标志
00 21
代表此类是一个public
的类
如果是public interface的话,那么这两个字节会变成 02 01
,0200代表是个接口,0001代表public,两个值进行与运算
4. 类索引、父类索引、接口索引集合
第一个为本类索引,指向常量池中第二项,常量池第二项指向第十四项com/szu/jvm/learn01_bytecode/L01_EmptyClass
第二个为父类索引,指向常量池第三项,第三项指向第十五项java/lang/Object
接口可以有多个,但是本类中暂时没写任何实现接口,所以此处为0
5.字段表集合
用于描述类中声明的变量,Java语言中的“字段”(Field)包括类级变 量以及实例级变量,但不包括在方法内部声明的局部变量。
字段可以包括的修饰符有字段的作用域(public、private、protected修饰 符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否 强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、 字段名称。
此时我们使用下边的代码来看类文件
package com.szu.jvm.learn01_bytecode;
/*
* @Author 郭学胤
* @University 深圳大学
* @Description
* @Date 2021/2/20 13:33
*/
public class L01_EmptyClass {
private int m;
}
00 01
代表字段表长度
00 02
access_flags,访问修饰符(见下表)
00 04
表示字段名称,指向常量池中的元素
00 05
代表字段类型,指向常量池中的元素
6.方发表集合
public class L01_EmptyClass {
private int m;
}
00 01
代表方法数量(此时只有一个默认的构造方法)
00 01
,access_flags
00 06
,方法名称,指向常量池的6号元素,构造方法用<init>
表示
00 07
,方法的描述符,也就是返回值类型,void用<()V>
表示
00 01
参数数量
00 08
参数的名称,指向常量池中的元素
7.属性表集合
7.1 code属性 + 异常表
方法体里面的代码经过编译器处理之后,最终变为字节码指令存储在Code属性内。
code属性中有
- 局部变量表所需的存储空间
- 操作数栈深度的最大值。
- code_length和code用来存储编译后生成的字节码指令,一个方法不允许超过65535条字节码指令
- 异常表
public int inc() {
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3;
}
}
// 编译后的ByteCode字节码及异常表
public int inc();
Code:
Stack=1, Locals=5, Args_size=1
0: iconst_1 // try块中的x=1
1: istore_1
2: iload_1 // 保存x到returnValue中,此时x=1
3: istore 4
5: iconst_3 // finaly块中的x=3
6: istore_1
7: iload 4 // 将returnValue中的值放到栈顶,准备给ireturn返回
9: ireturn
10: astore_2 // 给catch中定义的Exception e赋值,存储在变量槽 2中
11: iconst_2 // catch块中的x=2
12: istore_1
13: iload_1 // 保存x到returnValue中,此时x=2
14: istore 4
16: iconst_3 // finaly块中的x=3
17: istore_1
18: iload 4 // 将returnValue中的值放到栈顶,准备给ireturn返回
20: ireturn
21: astore_3 // 如果出现了不属于java.lang.Exception及其子类的异常才会走到这里
22: iconst_3 // finaly块中的x=3
23: istore_1
24: aload_3 // 将异常放置到栈顶,并抛出
25: athrow
Exception table:
from to target type
0 5 10 Class java/lang/Exception
0 5 21 any
10 16 21 any
7.2 Exceptions属性
属性的作用是列举出方法中可能抛出的受查异常,不要与前面刚刚讲解完的异 常表产生混淆。
7.3 LineNumberTable
属性
用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。 它并不是运行时必需的属性,但默认会生成到Class文件之中。
7.4 其他
-
InnerClasses
属性 -
Signature属性
-
等等吧
常量池内容表