欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Class文件结构

程序员文章站 2022-12-20 20:35:23
1.1 概述Java规范BAT面试题类文件结构有几个部分?字节码都有哪些?Integer x = 5 ;int y = 5;比较x==y都经过了哪些步骤public class IntegerTest { public static void main(String[] args) { Integer i1 = 10; Integer i2 = 10; System.out.println(i1 == i2);//true...

1.1 概述

Java规范

Class文件结构
BAT面试题

  • 类文件结构有几个部分?
  • 字节码都有哪些?Integer x = 5 ;int y = 5;比较x==y都经过了哪些步骤
public class IntegerTest {
    public static void main(String[] args) {
        Integer i1 = 10;
        Integer i2 = 10;
        System.out.println(i1 == i2);//true
        
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//false
    }
}
public class StringTest {
    public static void main(String[] args) {
        String str = new String("hello") + new String("world");
        String str1 = "helloworld";
        System.out.println(str == str1);//false
        String str2 = new String("helloworld);
        System.out.println(str == str2);//false
    }
}
public class SonTest {
    public static void main(String[] args) {
        Father f = new Son();
        System.out.println(f.x);
    }
}

class Father {
    int x = 10;

    public Father() {
        this.print();
        x = 20;
    }

    public void print() {
        System.out.println("Father.x = " + x);
    }
}

class Son extends Father {
    int x = 30;

    public Son() {
        this.print();
        x = 40;
    }

    public void print() {
        System.out.println("Son.x = " + x);
    }
}
//成员变量(非静态)的赋值过程:
//1.默认初始化
//2.显示初始化|代码块中初始化
//3.构造器中初始化
//4.有了对象后,对象.属性的方式进行赋值
//Son.x=0
//Son.x=30
//20

1.2 虚拟机的基石:Class文件

  • 源代码经过编译器编译后便会生成一个字节码文件,字节码是一种二进制的类文件,他的内容是JVM的指令,而不像C、C++经由编译器直接生成机器码
  • Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)及跟随其后的零至多个代表次操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码

Class文件结构

1.3 Class文件结构

官方文档

  • Class类的本质

任何一个Class文件都对应着唯一一个类或接口的定义信息,Class文件是一组以8位字节为基础单位的二进制流

  • Class文件格式

没有任何分隔符,采用类似于C语言结构体的方式进行数据存储,只有两种数据类型:无符号数和表

无符号数以u1、u2、u4、u8分别代表1\2\4\8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或按照UTF-8编码构成字符串值

表是多个无符号数或其他表作为数据项构成的符号数据类型,表以"_info"结尾,表用于描述有层次关系的符号结构的数据,整个class文件本质上就是一张表

代码举例

public class Demo {
    private int num = 1;
    
    public int add() {
        num = num + 2;
        return num;
    }
}

对应的字节码文件:
Class文件结构
class文件的总体结构如下:

  1. 魔数
  2. class文件版本
  3. 常量池
  4. 访问标志
  5. 类索引,父类索引,接口索引集合
  6. 字段表集合
  7. 方法表集合
  8. 属性表集合
类型 名称 说明 长度 数量
u4 magic 魔数,识别class文件格式 4个字节 1
u2 minor_version 副版本号,小版本 2个字节 1
u2 major_version 主版本号,大版本 2个字节 1
u2 constant_pool_count 常量池计数器 2个字节 1
cp_info constant_pool 常量池表 n个字节 constant_pool_count-1
u2 access_flags 访问标识 2个字节 1
u2 this_class 类索引 2个字节 1
u2 super_class 父类索引 2个字节 1
u2 interfaces_count 接口计数器 2个字节 1
u2 interfaces 接口索引集合 2个字节 interfaces_count
u2 fields_count 字段计数器 2个字节 1
field_info fields 字段表 n个字节 fields_count
u2 methods_count 方法计数器 2个字节 1
method_info methods 方法表 n个字节 methods_count
u2 attributes_count 属性计数器 2个字节 1
attribute_info attributes 属性表 n个字节 attributes_count

Class文件结构

1.3.1魔数(Magic Number)

  • 确定文件是否为一个能被虚拟机接受的有效合法class文件,魔数是class文件的标识符
  • 魔术值固定为0xCAFEBABE,不会改变
  • 如果一个class文件以0xCAFEBABE开头,虚拟机在文件校验的时候会抛出错误:

Error: A JNI error has occurred, please check your installation and try again

Exception in thread “main” java.lang.ClassFormatError: Incompatible magic value 1885430635 in class file StringTest

1.3.2 class文件版本

  • 5,6字节是编译的副版本minor_version,7,8字节是主版本major_version
  • 共同构成了class文件的格式版本号,例如主版本为M,副版本为m,版本号就是M.m
  • 版本号和Java编译器对应关系
主版本(十进制) 副版本(十进制) 编译器版本
45 3 1.1
46 0 1.2
47 0 1.3
48 0 1.4
49 0 1.5
50 0 1.6
51 0 1.7
52 0 1.8
53 0 1.9
54 0 1.10
55 0 1.11
  • 高版本的Java虚拟机可以执行由低版本编译器生成的Class文件,反之会抛出java.lang.UnsupportedClassVersionError异常
  • 在开发时,注意开发编译的JDK版本和生产环境中的JDK版本是否一致

1.3.3 常量池

  • 常量池对于class文件中的字段和方法解析有很重要的作用
  • 版本号之后,紧跟着的是常量池的数量以及若干个常量池表项
  • 常量池中常量的数量是不固定的,所以在常量池的入口放置一项u2类型的无符号数,代表常量池容量计数值(constant_pool_count),这个容量计数从1不是0开始的
  • 常量池表项中,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

常量池计数器

Class文件结构
0x0016,十进制22,实际上只有21项常量,索引范围为1-21,他把第0项常量空出来了。为了满足后面某些指向常量池的索引值的数据在特性情况下需要表达“不引用任何一个常量池项目”的含义

常量池表

  • constant_pool是一种表结构,以1~constant_pool_count-1为索引,表明了后面有多少个常量项
  • 常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)
  • 它包含了class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。第一个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte(标记字节)
类型 标志 描述
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_ integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_ Double_info 6 双精度浮点型字面量
CONSTANT_ Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标志方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

字面量和符号引用

常量 具体的常量 备注
字面量 文本字符串 String str=“hahaha”
声明为fianl的常量值 final int num=10
符号引用 类和接口的全限定名 全限定名:包名的".“替换成”/",最后一般加入";"
字段的名称和描述符 add()方法,num字段的简单名称是add和num
方法的名称和描述符 描述符作用是用来描述字段的数据类型、方法的参数列表和返回值
  • 根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示
标志符 含义
B 基本数据类型byte
C 基本数据类型char
D 基本数据类型double
F 基本数据类型float
I 基本数据类型int
J 基本数据类型long
S 基本数据类型short
Z 基本数据类型boolean
V 代表void类型
L 对象类型,如:Ljava/lang/Object
[ 数组类型,代表一维数组,如:double[][][] is [[[D

常量类型和结构

Class文件结构
Class文件结构
总结:

  • 常量池,可以理解为class文件之中的资源仓库,是class文件结构中与其他项目关联最多的数据类型,后面很多数据类型都会指向此处,也是占用class文件空间最大的数据项目之一
  • 常量池为什么需要包含这些内容?Java不会像C那样有”连接"操作,在class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再翻译到具体的内存地址中

1.3.4 访问标志(access_flag)

常量池后,紧跟着访问标记,用于识别一些类或接口层次的访问信息,包括:这个class是类还是接口;是否定义为public;是否定义为abstract;是否声明final等

标志名称 标志值 含义
ACC_PUBLIC 0x0001 标志位public类型
ACC_FINAL 0x0010 标志被声明位final,只有类可以设置
ACC_SUPER 0x0020 标志允许使用invokespecial字节码指令的新语义,JDK1.0.2后编译出来的类的这个标志默认为真
ACC_INTERFACE 0x0200 标志这个一个接口
ACC_ABSTRACT 0x0400 是否为abstract,对于接口或抽象类,此标志为真,其他类型为假
ACC_SYNTHETIC 0x1000 标志此类并非由用户代码产生,由编译器产生的类,没有源码对应
ACC_ANNOTATION 0x2000 标志这个一个注释
ACC_ENUM 0x4000 标志这个一个枚举
  • ACC_INTERFACE标志的class文件表示的是接口而不是类
  • 注解类型必须设置ACC_ANNOTAION,,如果设置了ACC_ANNOTAION,那么也必须设置ACC_INTERFACE
  • ACC_ENUM表明该类或其父类是枚举类型

1.3.5 类索引,父类索引,接口索引集合

在访问标记后,会指定该类的类别、父类类别以及实现的接口

长度 含义 备注
u2 this_class 指向常量池的索引,提供了类的全限定名
u2 super_class superclass指向的父类不能是final
u2 interfaces_count 表示当前类或接口的直接超接口数量
u2 interfaces[interfaces_count] 接口索引集合,interfaces[0]对应的是源代码最左边的接口
  • 类索引用于确定这个类的全限定名
  • Java语言不允许多重继承,除了java.lang.Object外,所有Java类都有父类

1.3.6 字段表集合(Fields)

  • 用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,不包括方法内部、代码块内部声明的局部变量
  • 字段叫什么名、字段被定义为声明数据类型,这些都是无法固定的,只能引用常量池中的常量来描述
  • 描述了每个字段的完整信息,比如字段的标识符、访问修饰符(public、private或protected)、类变量还是实例变量(static)、是否是常量(final)等

字段计数器(fields_count)

表示当前class文件fields表的成员个数

字段表集合(fields[])

一个字段的信息包含如下信息,各个修饰符都是布尔值:

  • 作用域(public、private、protected)
  • 实例变量还是类变量(static)
  • 可变性(final)
  • 并发可见性(volatile,是否强制从主内存读写)
  • 可否序列化(transient)
  • 字段数据类型(对象、数组…)
  • 字段名称

字段表结构

类型 名称 含义 数量
u2 access_flags 访问标志 1
u2 name_index 字段名索引 1
u2 descrtptor_index 描述符索引 1
u2 attributes_count 属性计数器 1
attribute_info attributes 属性集合 attributes_count

字段访问标识(access_flags)

标志名称 标志值 含义
ACC_PUBIC 0x0001 字段是否为public
ACC_PRIVATE 0x0002 字段是否为private
ACC_PROTECTED 0x0004 字段是否为protected
ACC_STATIC 0x0008 字段是否为static
ACC_FINAL 0x0010 字段是否为fianl
ACC_VOLATILE 0x0040 字段是否为volatile
ACC_TRANSTENT 0x0080 字段是否为transient
ACC_SYNCHETIC 0x1000 字段是否由编译器自动产生
ACC_ENUM 0x4000 字段是否为enum

字段名索引(name_index)

常量池中索引,字段的名字

描述符索引(descrtptor_index)

标志符 含义
B byte
C char
D double
F float
I int
J long
S short
Z boolean
L reference 一个名为Classname的实例
[ reference,一个一维数组

字段的属性集合

一个字段还可能拥有其他一些属性,比如初始化值,注释信息等

以常量属性为例,结构为
ConstantValue_attribute{
u2 attribute_name_index;
u4 attribute_length;//常量属性,attribute_length恒为2
u2 constantvalues_index;
}

1.3.7 方法表集合

  • 每一个method_info都都想着一个类或接口中的方法信息
  • 如果这个方法不是抽象的或不是native的,那么字节码中会体现出来
  • methods只描述类或接口中声明的方法,不包括从父类或父接口继承的方法

方法表methods[]

方法表的结构实际跟字段表是一样的

类型 名称 含义 数量
u2 access_flags 访问标志 1
u2 name_index 方法名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 属性计数器 1
attribute_info attributes 属性集合 attributes_count

方法表访问标志

标志名称 标志值 含义
ACC_PUBIC 0x0001 public,方法可以从包外访问
ACC_PRIVATE 0x0002 private,方法只能本类中访问
ACC_PROTECTED 0x0004 protected,方法在自身和子类可以访问
ACC_STATIC 0x0008 static,静态方法

1.3.8 属性表集合

  • 方法表集合之后的属性表,指的是class文件新携带的辅助信息,比如class文件的源文件的抿成,以及任何带有RetentionPolicy.CLASS或RetentionPolicy.RUNTIM的注解
  • 此外,字段表、方法表都有自己的属性表,用于描述某些场景专有的信息

attributes[] 属性表

属性的通用格式

类型 名称 数量 含义
u2 attribute_name_index 1 属性名索引
u4 attribute_length 1 属性长度
u1 info attribute_length 属性表

属性类型

  • 属性表实际上有很多类型,上面看到的Code属性只是其中一种,Java8中定义了23种属性
属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量池
Deprecated 类,方法,字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当一个类为局部类或匿名类才拥有这个属性
InnerClass 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
StackMapTable Code属性 JDK1.6新增的,供新的类型检查器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
Signature 类,方法,字段表 用于支持泛型情况下的方法签名
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 用于存储额外的调试信息
Synthetic 类,方法,字段表 标志方法或字段为编译器自动生成的
LocalVariableTypeTable 使用特性前面替代描述符,是为了引用泛型语法后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类,方法,字段表 为动态注解提供支持
RuntimeInvisibleAnnotations 类,方法,字段表 用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotation 方法表 作用与RuntimeInvisibleAnnotations类似,作用对象为方法
RuntimeInvisibleParameterAnnotation 方法表 作用与RuntimeInvisibleAnnotations类似,作用对象哪个为方法参数
AnnotationDefault 方法表 用于记录注解类元素的默认值
BootstrapMethods 类文件 用于保存invokeddynamic指令引用的引导方式限定符
  • Code属性
类型 名称 数量 含义
u2 attribute_name_index 1 属性名索引
u4 attribute_length 1 属性长度
u2 max_stack 1 操作数栈深度的最大值
u2 min_locals 1 局部变量表所需的续存空间
u4 code_length 1 字节码指令的长度
u1 code code_length 存储字节码指令
u2 exception_table_length 1 异常表长度
exception_info exception_table exception_length 异常表
u2 attributes_count 1 属性集合计数器
attribute_info attributes attributes_count 属性集合
  • LineNumberTable属性

LineNumberTable属性是可选变长属性,位于Code属性的属性。是用来描述Java源码行号与字节码行号之间的对应关系

LineNumberTable_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
	u2 line_number_table_length;
	{
		u2 start_pc;
		u2 line_number;
	} line_number_table[line_number_table_length];
}
  • LocalVariableTable属性

LocalVariableTable属性是可选变长属性,位于Code属性的属性。它被调试器用于确定方法在执行过程中局部变量的信息

LocalVariableTable_attribute{
	u2 attribute_name_index;
	u4 attribute_length;
	u2 local_variable_table_length;
	{
		u2 start_pc;
		u2 length;
		u2 name_index;
		u2 descriprot_index;
		u2 index;
	} local_variable_table[local_variable_table_length];
}
  1. start_pc + length 表示这个变量在字节码中的生命周期起始和结束的偏移位置(this 生命周期从头0到结尾10)
  2. index 就是这个变量在局部变量表中的槽位(槽位可复用)
  3. name 就是变量名称
  4. Descriptor 表示局部变量类型描述

  • SourceFile属性
类型 名称 数量 含义
u2 attribute_name_index 1 属性名索引
u4 attribute_length 1 属性长度,始终是2
u2 sourcefile_index 1 源码文件索引

1.4 使用javap执行解析Class文件

1.4.1 javac -g操作

java xxx.java不会生成对应的局部变量表的信息
javac -g xx.java生成对应的局部变量表等信息

1.4.2 javap的用法

Class文件结构
常用-v(-verbose)输出的最全 -l -c参数

本文地址:https://blog.csdn.net/u014215131/article/details/109543125

相关标签: JVM java