首页 > 其他分享 >5. 类文件结构

5. 类文件结构

时间:2023-12-21 21:25:03浏览次数:40  
标签:ACC 文件 lang 常量 Utf8 info Class 结构

Class 类文件的结构

Class 文件是一组以 8 个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个 Class 文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在

  • 当遇到需要占用 8 个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个 8 个字节进行存储

Class 文件格式采用一种类似于 C 语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表

  • 无符号数属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串值
  • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以 _info 结尾。表用于描述有层次关系的复合结构的数据,整个 Class 文件本质上也可以视作是一张表

无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的集合

类型 名称 数量
u4 magic 1
u2 minjor_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

1. 查看 Class 文件结构

除了二进制编辑器,还可以使用 javap -v 类名 查看文件结构

public final class Test {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}
  • 使用 javap -v 类名 > test.txt 指令将结果导出
Classfile /D:/workspace/HelloWorld/out/production/HelloWorld/com/Test.class
  Last modified 2023-12-20; size 523 bytes
  MD5 checksum b286dfd2cb654b858bfde8ad44c705dd
  Compiled from "Test.java"
public final class com.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // Hello World
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // com/Test
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/Test;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               Test.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               com/Test
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public com.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/Test;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "Test.java"

2. 魔数

每个 Class 文件的头 4 个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件

  • 使用魔数而不是扩展名来进行识别主要是基于安全考虑,因为文件扩展名可以随意改动。文件格式的制定者可以自由地选择魔数值,只要这个魔数值还没有被广泛采用过而且不会引起混淆

Class 文件的魔数取得很有浪漫气息,值为 0xCAFEBABE(咖啡宝贝)

3. Class 文件的版本

紧接着魔数的 4 个字节存储的是 Class 文件的版本号:第 5 和第 6 个字节是次版本号(Minor Version),第 7 和第 8 个字节是主版本号(Major Version)

Java 的版本号是从 45 开始的,JDK1.1 之后的每个 JDK 大版本发布主版本号向上加 1,高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件,JVM 规范在 Class 文件校验部分明确要求了即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的 Class 文件

4. 常量池

紧接着主、次版本号之后的是常量池入口,常量池可以比喻为 Class 文件里的资源仓库,它是 Class 文件结构中与其他项目关联最多的数据,通常也是占用 Class 文件空间最大的数据项目之一

由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项 u2 类型的数据,代表常量池容量计数值(constant_pool_count)。与 Java 中语言习惯不同,这个容量计数是从 1 而不是 0 开始的

  • 索引值设置为 0,表达不引用任何一个常量池项目的含义
  • Class 文件结构中只有常量池的容量计数是从 1 开始

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于 Java 语言层面的常量概念,如文本字符串、被声明为 final 的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量

  • 被模块导出或者开放的包
  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
  • 方法句柄和方法类型
  • 动态调用点和动态常量

在 Class 文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。当虚拟机做类加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中

常量池中每一项常量都是一个表,常量表中分别有 17 种不同类型的常量,表结构起始的第一位都是 u1 类型的标志位,代表着当前常量属于哪种常量类型

类型 标志 描述
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_Dynamic_info 17 表示一个动态计算常量
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点
CONSTANT_Moudule_info 19 表示一个模块
CONSTANT_Package_info 20 表示一个模块中开放或导出的包

5. 访问标志

在常量池结束之后,紧接着的 2 个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为 public
ACC_FINAL 0x0010 是否为 final
ACC_SUPER 0x0020 JDK1.0.2 后都为真
ACC_INTERFACE 0x0200 是否为接口
ACC_ABSTRACT 0x0400 标识为抽象类型,接口与抽象类都为真
ACC_SYNTHETIC 0x1000 是否由编译器自动生成
ACC_ANNOTATION 0x2000 是否为注解
ACC_ENUM 0x4000 是否为枚举
ACC_MODULE 0x8000 是否为模块

  • 访问标志为 0x0001 | 0x0010 | 0x0020 = 0x0031

6. 类索引、父类索引与接口索引集合

类索引(this_class)和父类索引(super_class)都是一个 u2 类型的数据,而接口索引集合(interfaces)是一组 u2 类型的数据的集合,Class 文件中由这三项数据来确定该类型的继承关系

类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。除了 java.lang.Object 外,所有类的父类索引都不为 0。接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 关键字(如果这个 Class 文件表示的是一个接口,则应当是 extends 关键字)后的接口顺序从左到右排列在接口索引集合中

7. 字段表集合

字段表(field_info)用于描述接口或者类中声明的变量。Java 语言中的字段(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量

字段可以包括的修饰符有字段的作用域(public、private、protected)、是实例变量还是类变量(static)、可变性(final)、并发可见性(volatile)、可否被序列化(transient)、字段数据类型(基本类型、对象、数组)、字段名称

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count
  • 在代码中添加字段进行演示
private static volatile String[] strArr;

7.1 access_flags

字段修饰符放在 access_flags 项目中

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为 public
ACC_PRIVATE 0x0010 是否为 private
ACC_PROTECTED 0x0020 是否为 proctected
ACC_STATIC 0x0200 是否为 static
ACC_FINAL 0x0400 是否为 final
ACC_VOLATILE 0x1000 是否为 volatile
ACC_TRANSIENT 0x2000 是否为 transient
ACC_SYNTHETIC 0x4000 是否由编译器自动生成
ACC_ENUM 0x8000 是否为 enum

  • 访问标志为 0x0002 | 0x0008 | 0x0040 = 0x004A

7.2 name_index

对常量池项的引用,代表着字段的简单名称。没有类型和参数修饰字段名称

7.3 descriptor_index

对常量池项的引用,代表着字段的描述符

描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型以及代表无返回值的 void 类型都用一个大写字符来表示,而对象类型则用字符 L 加对象的全限定名来表示

  • 全限定名:将类全名中的 . 替换成 /,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个 ; 号表示全限定名结束
  • 对于数组类型,每一维度将使用一个前置的 [ 字符来描述,如 String[][] 类型的二维数组将被记录成 [[Ljava/lang/String;,一个整型数组 int[] 将被记录成 [I
标识字符 含义
B 基本类型 byte
C 基本类型 char
D 基本类型 double
F 基本类型 float
I 基本类型 int
J 基本类型 long
S 基本类型 short
Z 基本类型 boolean
V 特殊类型 void
L 对象类型

7.4 attributes_count 与 attributes

字段表所包含的固定数据项目到 descriptor_index 为止就全部结束了,不过在 descriptor_index 之后跟随着一个属性表集合,用于存储一些额外的信息,字段表可以在属性表中附加描述零至多项的额外信息

8. 方法表集合

Class 文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式,方法表的结构如同字段表一样。仅在访问标志和属性表集合的可选项中有所区别

  • 方法访问标志
标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为 public
ACC_PRIVATE 0x0002 是否为 private
ACC_PROTECTED 0x0004 是否为 proctected
ACC_STATIC 0x0008 是否为 static
ACC_FINAL 0x0010 是否为 final
ACC_SYNCHRONIZED 0x0020 是否为 synchronized
ACC_BRIDGE 0x0040 是否为编译器产生的桥接方法
ACC_VARARGS 0x0080 是否接受不定参数
ACC_NATIVE 0x0100 是否为 native
ACC_ABSTRACT 0x0400 是否为 abstract
ACC_STRICT 0x0800 是否为 strictfp
ACC_SYNTHETIC 0x1000 是否由编译器自动生成

方法里的 Java 代码,编译成字节码指令之后,存放在方法属性表集合中一个名为 Code 的属性里面,属性表是 Class 文件格式中最具扩展性的一种数据项目

与字段表集合相对应地,如果父类方法在子类中没有被重写,方法表集合中就不会出现来自父类的方法信息。但同样地,有可能会出现由编译器自动添加的方法,最常见的便是类构造器 <clinit> 方法和实例构造器 <init> 方法

用描述符来描述方法时,按照先参数列表、后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号之内

private double doSomething(int i, String s, boolean[] boolArr) {
    return 0;
}
(ILjava/lang/String;[Z)D
# I 参数i
# Ljava/lang/String; 参数s
# [Z 参数boolArr
# D 返回值

9. 属性表集合

Class 文件、字段表、方法表都可以携带自己的属性表集合(attribute_info),以描述某些场景专有的信息

与 Class 文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格顺序,并且 JVM 规范允许只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,JVM 运行时会忽略掉它不认识的属性

深入理解Java虚拟机(第3版) - 表6-13 虚拟机规范预定义的属性

引用

标签:ACC,文件,lang,常量,Utf8,info,Class,结构
From: https://www.cnblogs.com/holyholic/p/17920129.html

相关文章

  • asp.net core 3.x 解决文件上传大小限制
    如在Kestrel服务器下可以选择在CreateHostBuilder方法中统一移除所有上传限制。也可在指定的action上添加[DisableRequestSizeLimit]属性解除限制。//第一种在CreateHostBuilder中添加配置。publicstaticIHostBuilderCreateHostBuilder(string[]args){......
  • java读取yaml文件
    应用场景自动化测试中,用例数据存放在yaml文件中 数据 person.yaml---id:1name:韧age:18---id:2name:qzcsbjage:19person2.yaml----id:3name:小韧age:18-id:4name:全栈测试笔记age:19person3.yaml----id:5......
  • JVM基础篇(三)-JVM结构-运行时数据区之栈帧
    栈帧栈帧的内部结构每个栈帧中存储着:局部变量表(LocalVariables)操作数栈(operandStack)(或表达式栈)动态链接(DynamicLinking)(或指向运行时常量池的方法引用)方法返回地址(ReturnAddress)(或方法正常退出或者异常退出的定义)一些附加信息并行每个线程下的栈都是私有的,因此每个线程都有自己各......
  • Week1——STL 与基础数据结构专题训练
    https://blog.csdn.net/qq_46025844/article/details/127948957 实训概要实训专题STL与基础数据结构专题训练实训目的掌握STL常用的算法、容器、容器适配器的使用方法。能够利用STL的算法、容器、容器适配器求解问题。题目列表A:摘苹果B:立方和C:计算个数D:后缀表达式的值E:做蛋糕......
  • 伪造网页数据(每个文件夹抽指定数量的图片并重命名)
    (承接"公共标,特殊标"的结构)1.对于指定父目录结构,每个文件夹抽指定数量的随机图片。importosimportshutilimportrandomdeffind_image_folders(parent_directory):#查找包含图片的文件夹image_folders=[]forroot,dirs,filesinos.walk(parent_direc......
  • 【数据结构】第二章——线性表(2)
    线性表的顺序表示导言大家好,很高兴又和各位见面啦!!!在上一个篇章中,我们简单了解了一下线性表的基础知识以及一下重要的术语。在今天的篇章中我们将来开始正式介绍线性表的顺序存储——又称顺序表。我们将会在本章介绍什么是顺序表,对于顺序表的操作我们又应该如何实现。接下来,我们就来......
  • vue+element 上传文件及文件夹
    有需求需要做一个上传压缩包及文件夹的需求,记性不好,记录一下。HTML使用的element的upload和form表单,dropdown下拉菜单<div><el-col:span="24"><el-form-itemlabel="材料路径:"prop="fileName"><el-col:span="21&qu......
  • django+vue实现文件夹上传
    最近学django的文件上下传,网上的文件夹上下传压根没有,找了好几个,报错一大堆,没有一个能用,花里胡哨,可气!!!下面这个方法是我刚刚用过的,分享给大家。前端vue非常简单,template部分<inputtype="file"id="twos"webkitdirectory/><el-buttontype="primary"@click="sumfolder">文件夹......
  • .NETCore Nuget 发布包含静态文件 content file
    .NETCore在.csproj引用资源中标记pack配置<pack>true</pack>1例如 <ItemGroup> <ContentInclude="dotnetty.linux.pfx"> <pack>true</pack> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <......
  • Pytorch模型结构修改
    1.Var方差在pytorch中的差别:tlist=input.tolist()print(input)print(np.mean(tlist),np.var(tlist))print(torch.mean(input),torch.var(input))可以看到numpy与torch求出来的**Var**与**Mean**有差别原因:如下图为torch.var官网描述,其中存在一个矫正correctio......