JVM-类文件结构详解

类文件结构详解

引言

计算机是不能直接运行java代码的,要先由jvm运行编译后成字节码字节码文件再交由运行于不同平台上的JVM虚拟机去读取执行,从而实现一次编写,到处运行的目的。

为什么计算机不能直接运行java代码呢

java是高级语言,我们才能理解其逻辑,计算机是无法识别的,所以java代码要先编译成字节码文件,jvm将字节码转换为计算机能识别的指令才能运行

简介

class文件本质上是一个以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件中。

  • jvm根据其特定的规则解析该二进制数据,从而得到相关信息。
  • Class文件采用一种伪结构来存储数据,它有两种类型:无符号数和表。

Class文件结构

根据 Java 虚拟机规范,Class 文件通过 ClassFile 定义,有点类似 C 语言的结构体。

ClassFile 的结构如下:

ClassFile {
    u4             magic; //Class 文件的标志
    u2             minor_version;//Class 的小版本号
    u2             major_version;//Class 的大版本号
    u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//Class 的访问标记
    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口
    u2             fields_count;//Class 文件的字段属性
    field_info     fields[fields_count];//一个类会可以有多个字段
    u2             methods_count;//Class 文件的方法数量
    method_info    methods[methods_count];//一个类可以有个多个方法
    u2             attributes_count;//此类的属性表中的属性数
    attribute_info attributes[attributes_count];//属性表集合
}

魔数

🆎 (Magic Number)

每个 Class 文件的头 4 个字节称为魔数,唯一作用是检查这个文件是否为一个能被虚拟机接收的 Class 文件

    u4         magic; //Class 文件的标志

Class 文件版本号

🆎 (Minor&Major Version)

紧接着魔数的四个字节存储的是 Class 文件的版本号:第 5 和第 6 位是次版本号,第 7 和第 8 位是主版本号

  • 每当 Java 发布大版本(比如 Java 8,Java9)的时候,主版本号都会加 1
  • 高版本的jvm可以执行低版本编译器生成的class文件,但是低版本的jvm不能执行高版本编译器生成的class文件。所以得确保开发环境和生产环境的JDK版本一致
    u2             minor_version;//Class 的小版本号
    u2             major_version;//Class 的大版本号

常量池

🆎 (Constant Pool)

常量池主要存放两大常量

  • 字面量:类似java语言层面的常量概念
    • 文本字符串
    • 声明为 final 的常量值
    • 基本数据类型的值
    • 其他
  • 符号引用:属于编译原理方面的概念
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

在这里插入图片描述

    u2             constant_pool_count;//常量池的数量
    cp_info        constant_pool[constant_pool_count-1];//常量池

常量池中每一项常量都是一个表,这 14 种表有一个共同的特点:开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.

类型标志(tag)描述
CONSTANT_utf8_info1UTF-8 编码的字符串
CONSTANT_Integer_info3整形字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info长整型字面量
CONSTANT_Double_info双精度浮点型字面量
CONSTANT_Class_info类或接口的符号引用
CONSTANT_String_info字符串类型字面量
CONSTANT_Fieldref_info字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的符号引用
CONSTANT_MothodType_info16标志方法类型
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

.class 文件可以通过javap -v class类名 指令来看一下其常量池中的信息(javap -v class类名-> temp.txt :将结果输出到 temp.txt 文件)。

访问标志

🆎 (Access Flags)

在常量池结束之后,紧接着的两个字节代表访问标志,用于**识别一些类或者接口层次的访问信息**(这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等)

访问标志的含义如下:

标志名称标志值含义
ACC_PUBLIC0x0001是否为Public类型
ACC_FINAL0x0010是否被声明为final,只有类可以设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语义.
ACC_INTERFACE0x0200标志这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC0x1000标志这个类并非由用户代码产生
ACC_ANNOTATION0x2000标志这是一个注解
ACC_ENUM0x4000标志这是一个枚举

我们定义了一个 Employee 类

package top.snailclimb.bean;
public class Employee {
   ...
}

通过javap -v class类名 指令来看一下类的访问标志。

在这里插入图片描述

当前类、父类、接口索引集合

🆎 (This Class):类索引用于确定当前类的全限定名

🆎 (Super Class):父类索引用于确定这个类的父类的全限定名(Java是单继承,所以只有一个,除了除了 java.lang.Object 之外,所有类都有父类)

🆎 (Interfaces):接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按 implements (如果这个类本身是接口的话则是extends) 后的接口顺序从左到右排列在接口索引集合中。

    u2             this_class;//当前类
    u2             super_class;//父类
    u2             interfaces_count;//接口
    u2             interfaces[interfaces_count];//一个类可以实现多个接口

字段表集合

🆎 (Fields):存放多个字段表

字段表(field info)用于描述类或者接口声明的变量,包括类级变量(static)以及实例变量(普通成员变量),不包括在方法内部声明的局部变量。

field info(字段表) 的结构:

在这里插入图片描述

  • access_flags: 字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。
  • name_index: 对常量池的引用,表示的字段的名称;
  • descriptor_index: 对常量池的引用,表示字段和方法的描述符;
  • attributes_count: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;
  • attributes[attributes_count]: 存放具体属性具体内容。

上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型这些都是无法固定的,只能引用常量池中常量来描述。

    u2             fields_count;//Class 文件的字段的个数
    field_info     fields[fields_count];//一个类会可以有个字段

字段的 access_flag 的取值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b4cHEpmt-1651652267868)(C:/Users/%E9%BB%84%E5%87%AF%E5%AE%87/Desktop/%E6%98%A5%E6%8B%9B/JVM/JVM.assets/image-20201031084342859.png)]

方法表集合

🆎 (Methods)

methods_count 表示方法的数量,而 method_info 表示方法表。

Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。

method_info(方法表的) 结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BOVKGhx9-1651652267869)(C:/Users/%E9%BB%84%E5%87%AF%E5%AE%87/Desktop/%E6%98%A5%E6%8B%9B/JVM/JVM.assets/%E6%96%B9%E6%B3%95%E8%A1%A8%E7%9A%84%E7%BB%93%E6%9E%84.png)]

    u2             methods_count;//Class 文件的方法的数量
    method_info    methods[methods_count];//一个类可以有个多个方法

⚡️ 注意:

因为volatile修饰符和transient修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了synchronizednativeabstract等关键字修饰方法,所以也就多了这些关键字对应的标志。

方法表的 access_flag 取值:

属性表集合

(Attributes)

   u2             attributes_count;//此类的属性表中的属性数
   attribute_info attributes[attributes_count];//属性表集合

在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。

参考文章
https://snailclimb.gitee.io/javaguide/#/./docs/java/jvm/class-file-structure

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值