Java 入门指南:JVM(Java虚拟机)—— Java 类文件结构

news2024/11/15 19:25:40

文章目录

    • 字节码
      • JVM 与字节码
      • 字节码的生成过程
    • Class 文件结构
      • 魔数(Magic Number)
      • Class 文件版本号(Minor&Major Version)
      • 常量池(Constant Pool)
      • 访问标志(Access Flags)
      • 前类(This Class)、父类(Super Class)、接口(Interfaces)索引集合
      • 字段表集合(Fields)
      • 方法表集合(Methods)
      • 属性表集合(Attributes)
    • 索引值定位 class 文件中的位置

字节码

字节码是一种中间表示形式,它是由Java编译器将Java源代码编译得到的。字节码文件通常以 .class 为扩展名,每个Java类都会生成一个对应的字节码文件。字节码是一种低级的指令集合,它由一系列的指令组成,这些指令是为 JVM 设计的,而不是为特定的硬件平台设计的。

JVM 与字节码

在 Java 中,JVM 可以理解的代码是字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。

![[Pasted image 20240909161336.png]]

Groovy、Scala、JRuby、Kotlin 等语言都是运行在 Java 虚拟机之上。不同的语言被不同的编译器编译成 .class 文件最终运行在 Java 虚拟机之上。.class 文件的二进制格式可以使用 WinHexopen in new window 查看。

字节码的生成过程

当Java源代码被编译时,编译器会生成字节码文件。这个过程可以分为以下几个步骤:

  1. 词法分析:将源代码分解成一个个有意义的记号(Token)。
  2. 语法分析:将记号组合成符合 Java 语言语法的抽象语法树(AST)。
  3. 语义分析:检查语法树中的语义错误,如类型不匹配等。
  4. 代码生成:将语法树转换为字节码指令序列。

Class 文件结构

.class 文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。根据 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; //字段数量
    field_info     fields[fields_count]; //一个类可以有多个字段
    u2             methods_count; //方法数量
    method_info    methods[methods_count]; //一个类可以有个多个方法
    u2             attributes_count; //此类的属性表中的属性数
    attribute_info attributes[attributes_count]; //属性表集合
}

类文件的内容通常可以分为以下的部分:

![[Pasted image 20240909161639.png]]

通过 IDEA 插件 jclasslib 不光可以直观地查看某个类对应的字节码文件,还可以查看类的基本信息、常量池、接口、属性、函数等信息。

魔数(Magic Number)

u4             magic; // Class 文件的标志

每个 Class 文件的头 4 个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件。Java 规范规定魔数为固定值:0xCAFEBABE

JVM 会在验证阶段检查 class 文件是否以该魔数开头,如果读取的文件不是以这个魔数开头,Java 虚拟机将拒绝加载它,并会抛出 ClassFormatError

Class 文件版本号(Minor&Major Version)

u2             minor_version; // Class 的小版本号
u2             major_version; // Class 的大版本号

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

每当 Java 发布大版本(比如 Java 8,Java9)的时候,主版本号都会加 1。你可以使用 javap -v 命令来快速查看 Class 文件的版本号信息。

高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。所以,我们在实际开发的时候要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。

常量池(Constant Pool)

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

紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1常量池计数器是从 1 开始计数的,将第 0 项常量空出来是有特殊考虑的,索引值为 0 代表“不引用任何一个常量池项”)。

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

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

常量池中每一项常量都是一个表,这 14 种表有一个共同的特点:

  • 开始的第一位是一个 u1 类型的标志位
  • -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型。
类型标志 (tag)描述
CONSTANT_utf8_info1UTF-8 编码的字符串
CONSTANT_Integer_info3整形字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_FieldRef_info9字段的符号引用
CONSTANT_MethodRef_info10类中方法的符号引用
CONSTANT_InterfaceMethodRef_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的符号引用
CONSTANT_MethodType_info16标志方法类型
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点
.class 文件可以通过 javap -v class类名 指令来看一下其常量池中的信息(javap -v class类名-> temp.txt:将结果输出到 temp.txt 文件)。

访问标志(Access Flags)

u2             access_flags; // Class 的访问标记

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

总共有 16 个标记位可供使用,但常用的只有其中 7 个:

![[Pasted image 20240909164019.png]]

标识符标记位描述
ACC_PUBLIC0x0001声明为公共的;可以从其包之外访问
ACC_FINAL0x0010声明为最终的;不允许有子类
ACC_SUPER0x0020在通过 invokespecial 指令调用时,特别处理超类方法
ACC_INTERFACE0x0200是一个接口,不是一个类
ACC_ABSTRACT0x0400声明为抽象的;不能实例化
ACC_SYNTHETIC0x1000声明为合成的;不在源代码中存在
ACC_ANNOTATION0x2000声明为注解类型
ACC_ENUM0x4000声明为枚举类型
ACC_MODULE0x8000声明为模块类型

通过 javap -v class类名 指令可以查看指定类的访问标志。

前类(This Class)、父类(Super Class)、接口(Interfaces)索引集合

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

Java 类的继承关系由类索引、父类索引和接口索引集合三项确定。类索引、父类索引和接口索引集合按照顺序排在访问标志之后,

类索引(this_class)用于确定这个类的全限定名(当前类的索引)。

父类索引(super_class)用于确定这个类的父类的全限定名(父类的索引)。

由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 Java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。

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

字段表集合(Fields)

u2             fields_count; // 字段数量
field_info     fields[fields_count]; // 一个类会可以有个字段

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

field info(字段表) 的结构:

field_info {
  u2 access_flag;
  u2 name_index;
  u2 description_index;
  u2 attributes_count;
}
  • access_flag:字段的作用域(public ,private,protected 修饰符),是实例变量还是类变量(static 修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。

  • name_index:字段名的索引,指向常量池中的 CONSTANT_Utf8_info, 比如说下面示例中的值就为 name。

  • description_index:字段的描述类型索引,也指向常量池中的 CONSTANT_Utf8_info,针对不同的数据类型,会有不同规则的描述信息。

  • attributes_count: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;

  • attributes [attributes_count]: 存放具体属性具体内容。

public class FieldsExample {
    private String name;
}

这段代码的字段只有一个,修饰符为 private,类型为 String,字段名为 name

方法表集合(Methods)

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

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

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

method_info(方法表的) 结构:

标识符标记位描述
ACC_PUBLIC0x0001声明为 public;可以从其包之外访问。
ACC_PRIVATE0x0002声明为 private;只能在其定义类中访问。
ACC_PROTECTED0x0004声明为 protected;可以从其包内或子类中访问。
ACC_STATIC0x0008声明为 static
ACC_FINAL0x0010声明为 final;不允许有子类。
ACC_SYNCHRONIZED0x0020声明为 synchronized;调用时被监视器包装。
ACC_BRIDGE0x0040一个桥接方法,由编译器生成。
ACC_VARARGS0x0080声明为可变参数。
ACC_NATIVE0x0100声明为 native;在 Java 之外的其他语言中实现。
ACC_ABSTRACT0x0400声明为 abstract;没有提供实现。
ACC_STRICT0x0800声明为 strictfp;浮点模式为 FP-strict。
ACC_SYNTHETIC0x1000声明为 synthetic;不在源代码中存在。

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

属性表集合(Attributes)

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

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

属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。

以下是包含属性表在内的完整的表示字段的结构

![[Pasted image 20240909170716.png]]

索引值定位 class 文件中的位置

通过索引值,可以定位到在 class 文件中的位置。

在 Java 类文件中,常量池是一个索引表,它从索引值 1 开始计数,每个条目都有一个唯一的索引。

  • 常量池计数器:在常量池之前,类文件有一个 16位 的常量池计数器,表示常量池中有多少项。它的值比实际常量数大1(因为索引从1开始)。

  • 常量池条目:每个常量池条目的开始是一个标签(1个字节),表明了常量的类型(如Class、Fieldref、Methodref等)。根据这个类型,后面跟着的数据结构也不同。

定位过程大致如下:

  • 读取常量池计数器:首先,从类文件的开头读取常量池计数器的值,确定常量池中有多少条目。

  • 遍历常量池:从常量池的第一项开始遍历。由于不同类型的常量长度不同,需要根据每个常量的类型来确定它的长度。

  • 根据索引定位:继续遍历,直到到达所需的索引值。每次遍历时,根据条目类型读取相应长度的数据,直到达到目标索引。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2120439.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pygame中Sprite类实现多帧动画3-3

4 使用自定义类MySprite 使用自定义类MySprite实现多帧动画的步骤是首先创建MySprite类的实例,之后使用相关函数对该实例进行操作。 4.1 创建MySprite类的实例 创建MySprite类的实例的代码如图12所示。 图12 创建MySprite类的实例的代码 其中,变量dr…

2024年CCPC网络赛A题题解 —— 军训Ⅰ(gym105336A)

个人认为很唐的一道题,考虑到不少人可能懒得写,我这里给大家发个代码叭,还有一点点题解(因为真的不是很难)。这是题面: 然后我来讲讲怎么做,不觉得会有多少人题目意思都理解不了叭?这…

Javaweb项目-调用接口-如何在服务器端跳转网页后显示并弹出对话框代码

Webapp 项目中在java包下新建一个服务端类 使用JOptionPane框架组件 调用showMessageDialog的方法实现 四个参数null,"这是一个信息对话框","信息",JOptionPane.INFORMATION_MESSAGE 还有确认对话框的代码showConfirmDialog package servlet;import java…

k9s 是什么?有什么功能?

目录 k9s 是什么? 有什么功能? 手动安装 K9s(Windwos) 将 k9s.exe 添加到系统 PATH 启动 K9s k9s 是什么? K9s 是一个命令行工具,用于通过一个图形化的终端界面(类似于图形化用户界面但在命…

【Linux】常用的命令

文章目录 lsls -l / touchcdpwdcatechovim打开文件编辑内容保存退出 mkdirrmmvcpmangreppsnetstat总结 : ls ls > list 列出当前目录下都有哪些内容(文件/目录) 直接输入 ls,是查看当前目录的情况;输入 ls/ 就是看…

检查你的防病毒软件是否可以阻止这 5 个测试恶意软件文件

从网络安全专家到你,每个人都知道你应该使用防病毒软件来保护你的电脑免受黑客、病毒和其他类型的网络威胁。 但即使你这些年来一直在努力使用防病毒程序,你怎么知道它真的有效呢? 安全专家已经想到了这一点,并创建了几种类型的…

TMS320F28335芯片及使用介绍

1、简介 CPU性能的好坏不仅取决于主频大小,还需要看其整体架构集成性能、运算能力与指令体系。TMS320C2000系列DSP集微控制器和高性能 DSP 的特点于一身,具有强大的控制和信号处理能力,能够实现复杂的控制算法。TMS320C2000 系列DSP 片上整合了Flash存储器、快速的AD转换器…

基于微信小程序+Java+SSM+Vue+MySQL的付费自习室预订管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSSMVueMySQL的付费自习室预订管理系统【…

【CMake编译报错小复盘】CMAKE_CUDA_ARCHITECTURES,CMake version,GCC version问题

今天在写大模型量化推理框架时遇到了一些编译上的错误,简单复盘一下问题和解决方案: 问题1:CMAKE_CUDA_ARCHITECTURES 报错信息: CMake Error: CMAKE_CUDA_ARCHITECTURES must be non-empty if set cmake和cuda相关的报错通常都…

linux进程间通信——进程间通信概念、最基本通信——管道文件

前言: 本节内容将要讲解进程间通信。 之前我们说过进程之间是相互独立的, 但是,相互独立并不代表不能进行数据的输送。就好比我和你是相互独立的, 但是我们可以成为朋友, 可以互赠礼物。 而我们一般而言的,…

【C语言】归并排序递归和非递归——动图演示

目录 一、归并排序思想1.1 基本思想1.2 大体思路 二、实现归并排序(递归)三、实现归并排序(非递归)3.1 实现思路:3.2 越界处理3.3 时间复杂度和空间复杂度 总结 一、归并排序思想 1.1 基本思想 归并排序(M…

RTMP和WebRTC使用场景有哪些差别?

省流版先说结论 直播领域,RTMP和WebRTC各有优势。如果直播场景对延迟有一定要求,但更注重稳定性和兼容性,那么RTMP可能是一个更好的选择。如果直播场景需要极低的延迟,并且用户主要在浏览器环境下进行观看和互动,那么…

Leangoo敏捷工具在缺陷跟踪(BUG)管理中的高效应用

在开发过程中,缺陷(BUG)管理一直是项目管理中的一个关键环节。及时发现并修复BUG,不仅能够提高产品质量,还能有效提升团队的工作效率和用户满意度。 在敏捷开发中,快速迭代和频繁交付的特点使得缺陷管理的…

Servlet的特性(一)

Servlet的主要用途: 接受、处理来自浏览器端(BS架构中的B端)的请求和用户输入 响应来自数据库或者服务端(BS架构中的S端)产生的数据到浏览器端,动态构建网页。 手动实现Servlet小程序 实现步骤 自定义一个类型,实现Servlet接口或者继承Ht…

Spring Boot 集成 Redisson 实现消息队列

包含组件内容 RedisQueue:消息队列监听标识RedisQueueInit:Redis队列监听器RedisQueueListener:Redis消息队列监听实现RedisQueueService:Redis消息队列服务工具 代码实现 RedisQueue import java.lang.annotation.ElementTyp…

GD32E230 RTC报警中断功能使用

GD32E230 RTC报警中断使用 GD32E230 RTC时钟源有3个,一个是内部RC振动器产生的40KHz作为时钟源,或者是有外部32768Hz晶振.,或者外部高速时钟晶振分频作为时钟源。 🔖个人认为最难理解难点的就是有关RTC时钟异步预分频和同步预分频的计算。在对…

C++第二节入门 - 缺省参数和函数重载

一、缺省参数 1、概念 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。 在调用该函数的时候&#xff0c;如果没有指定实参则采用该形参的缺省值&#xff0c;否则使用指定的实参&#xff01; #include<iostream> using namespace std;void Func(int a 0) {c…

2024 水博会,国信华源登场,数智创新助力水利高质量发展

9月4日-6日&#xff0c;由中国水利学会和中国水利工程协会共同主办的2024中国水博览会暨第十九届中国&#xff08;国际&#xff09;水务创新技术交流会在重庆国际博览中心召开。 本次水博会以“展水利前沿新技术 览新质生产力场景”为主题&#xff0c;国信华源携最新智能监测预…

【佳学基因检测】如何升级一个不再维护的软件包中的PHP代码?

如何升级一个不再维护的软件包中的PHP代码&#xff1f; 为什么要升级一个不再维护但是仍在使用的软件包中的PHP代码&#xff1f; 升级一个不再维护但仍在使用的软件包中的 PHP 代码是一个复杂但重要的过程。虽然这些软件包可能已经不再活跃地维护或更新&#xff0c;但升级其代…

通信工程学习:什么是ATM异步转移模式

ATM&#xff1a;异步转移模式 ATM&#xff1a;Asynchronous Transfer Mode&#xff08;异步转移模式&#xff09;是一种先进的通信技术&#xff0c;它采用固定长度的信元&#xff08;Cell&#xff09;作为信息传输、复用、交换及处理的基本单位&#xff0c;并通过异步时分复用的…