类文件结构
了解jvm后续的一切动作,先从字节码开始。它是一切发生的源头。
# 2.1 测试案例
# 2.1.1 源代码
/*
* 基本类结构
* */
public class ClassStruct {
private static String name = "JVM";
private static final int age = 18;
public static void main(String[] args) {
System.out.println("Hello " + name);
}
}
# 2.1.2 编译
1)maven定义编译的版本
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2)编译
mvn clean compile
# 2.2 字节码结构
# 2.2.1 二进制概览
1)vscode打开
2)class文件是一个二进制文件,转化后是16进制展示,实际上class文件就是一张表,它由以下数据项构成,这些数据项从头到尾严格按照以下顺序排列:
类型 | 名称 | 数量 | 描述 |
---|---|---|---|
u4 | magic | 1 | 魔数 |
u2 | minor_version | 1 | 次版本号 |
u2 | major_version | 1 | 主版本号 |
u2 | constant_pool_count | 1 | 常量计数 |
cp_info | constant_pool | constant_pool_count - 1 | 具体常量 |
u2 | access_flags | 1 | 访问标志 |
u2 | this_class | 1 | 类索引 |
u2 | super_class | 1 | 父类索引 |
u2 | interfaces_count | 1 | 接口索引 |
u2 | interfaces | interfaces_count | 具体接口 |
u2 | fields_count | 1 | 字段个数 |
field_info | fields | fields_count | 具体字段 |
u2 | methods_count | 1 | 方法个数 |
method_info | methods | methods_count | 具体方法 |
u2 | attributes_count | 1 | 属性个数 |
attribute_info | attributes | attributes_count | 具体属性 |
3)图示如下:
# 2.2.2 魔数与版本
1)魔数:
CAFEBABE,咖啡宝宝,固定的。
2)版本号:
34,换成10进制就是52
jdk的版本标记映射关系:
说明编译用的是jdk8,我们改成1.6,重新执行 mvn clean compile ,再来查看class文件试试:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
扩展
在开发中,经常会遇到类似Unsupported major.minor version 51.0的错误,一般情况下都是JDK版本不匹配造成的。 虽然jdk代码在执行时基本上向下兼容,但是!开发环境和服务器环境jdk最好一致,不要尝试这个坑。
区分和理解两个环境:编译环境,运行环境
# 2.2.3 常量池
再往下遵从相同的规律: 计数器(标注后面有多少个) + 对应个数的结构体
我们以常量池为例:
1)位置
2)结构说明
常量池记录了jvm内的一堆常量信息,这部分由 【2个字节计数】 + 【n个cp_info结构】组成
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。 字面量比较接近于 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。 而符号引用则属于编译原理方面的概念,包括了下面三类常量: 类和接口的全限定名(Fully Qualified Name)、字段的名称和描述符(Descriptor)、方法的名称和描述符
其中cp_info有多种类型:
- 直接类型,存的就是当前值,这种像Integer,Long等长度都是确定的
- 引用类型,存的是指向其他位置的指针
附:绿色代表指针,橙色代表直接类型
3)案例
下面以String为例,String是一种引用类,它会指向一个utf8类型来存储真实的信息
jdk提供了一个工具,javap,可以查看常量列表的详细内容:
javap -v ClassStruct.class
# 2.2.4 其他信息
1)说明
常量池之后,是紧挨的一系列信息,这些信息大同小异,无非就是值、或者引用
(参考上面2.3.3里的表格和图例)
- 访问标记:public abstract 等信息
- 类索引,class类型,最终指向一个utf8,标记当前类的名字
- 父类,同上
- 接口,2字节记录数量,后面记录多个接口类型
- 接下来是字段、方法、属性,都是2字节记录后面多少个,后面紧跟对应的结构体类型
2)注意事项
要看懂javap后的格式,明白这些格式,可以轻松看懂class结构
类型 | 标识符 | 案例 | 说明 |
---|---|---|---|
数组 | [ | [Ljava.lang.String | String数组 |
对象 | L | Lcom.test.Demo | |
基本类型 | 大写字母开头 | B=byte,I=int…… | |
组合类型
类型 | 案例 | 说明 |
---|---|---|
类里的属性、字段、方法等 | com.test.Demo.name:Ljava.lang.String | 英文点号隔开 |
标识什么类型 | com.test.Demo.getName:()Ljava.lang.String | 英文冒号隔开 |
方法 | (参数类型)返回值类型 | 英文括弧,后面是返回值类型 |
3)实例分析