白话说Java虚拟机原理系列【第二章】:Class字节码文件详解

news2024/11/18 4:29:01

前导说明:
本文基于《深入理解Java虚拟机》第二版和个人理解完成,
以大白话的形式期望能用大家都看懂的描述讲清楚虚拟机内幕,
后续会增加基于《深入理解Java虚拟机》第三版内容,并进行二个版本的对比

在这里插入图片描述

Class字节码文件的数据结构

class文件是一组以8字节为基础单位的二进制流,各个数据项严格按照顺序紧凑排列在class文件中,中间没有任何分隔符,class文件中存储的几乎是全部程序运行的程序。JVM规范规定,class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表。

无符号数:属于基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。

表:由多个无符号数或者其他表作为数据项构成的复合数据类型,所有的表都习惯以"_info"结尾。
表主要用于描述,比如方法、字段等数据项。需要注意的是class文件是没有分隔符的,所以每个的二进制数据类型都是严格定义的。

class文件中用到的所有的数据类型的定义如下图:[其中"_info"结尾的是"表"类型的定义]
在这里插入图片描述

1.魔数

每个class文件的头4个字节称为魔数,唯一的作用就是用来确认该文件是否能被JVM识别,class文件的魔数为0xCAFEBABE,和文件后缀名基本一个意义,只不过优于后缀名,因为后缀名可以随便修改,这个却不容易修改,所以更能用于唯一标识一类文件。

2.次版本号(minor_version)

紧挨魔数的2个字节是次版本号。

3.主版本号(major_version)

紧挨次版本号的2个字节是主版本号。

作用:当前文件中的版本号用来与执行该文件的JVM用的JDK版本匹配,如果不能兼容将拒绝执行。如下:
0X0034(对应十进制的50):JDK1.8
0X0033(对应十进制的50):JDK1.7
0X0032(对应十进制的50):JDK1.6
0X0031(对应十进制的49):JDK1.5
0X0030(对应十进制的48):JDK1.4
0X002F(对应十进制的47):JDK1.3
0X002E(对应十进制的46):JDK1.2

4.常量池

紧挨版本号,是一个表的类型,存放的都是class文件的重要资源数据,java文件被编译后将会把一些数据存于此结构。

Constant_Pool_Count

常量池的入口开始有一个u2即2个字节来表示常量池的数据项的个数,索引值从1开始,比如如果是大小=21,即共有常量池数据项20个。

第0项索引的常量具有特殊意义,如果某些指向常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可以将索引值置为0来表示。

常量池中数据的类型:字面量/符号引用

字面量:比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
如:
int i = 1;把整数1赋值给int型变量i,整数1就是Java字面量,
同样String s = "abc"中的abc也是字面量。

符号引用:符号引用属于编译原理方面的概念,包括了下面三类常量:

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

初识常量池解析:

Java代码在运行Javac时,进行编译成class文件的动作,编译完成后常量池中的内容就已经生成了。而此时常量池中的字段、方法是没有在内存中的最终分布情况的,因为类还没有被加载。所以此时内部叫做符号引用。即这些字段、方法不经过运行期的转换(常量池解析)是无法得到真正的内存入口地址,也就无法被JVM使用。
而当JVM装载该class文件后会需要进行一系列的连接/解析(见《类加载器装载过程》篇)以及后续的对象创建,此过程就会产生内存的真实分配,然后保存在方法区的class文件信息将会被重新翻译、解析成真正的内存地址,这就是把符号引用替换成了直接引用,也即常量池的解析过程。

初识直接引用:

常量池中可以直接分配内存的数据:有一些特殊数据,比如private、static、final修饰的变量或方法和构造方法,这些就是跟静态绑定相关的变量或方法,会在编译时就确定内存的分配,所以可以称为是字面量的范畴,而只有符号引用才需要去动态的经过常量池解析后指向真正的内存空间,即变为直接引用。

注意:后边的方法区的方法调用会讲到,符号引用转为直接引用,在类加载阶段只做了一部分的转换,而另一部分需要在运行时做转换,这部分是动态绑定的内容。


常量池Constant_Pool的结构

常量池中每一项常量都是一个表,即常量池是一个复合结构。共有14种不同的表结构类型,不过他们有一个共同点,都是由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型。
在这里插入图片描述

命令javap可以查看class字节码内容。
javap -v -c -s -l Main.class

描述符:
1.成员变量(包括静态成员变量和实例变量) 和方法都有各自的描述符。
2.对于字段(就是成员变量的意思)而言,描述符用于描述字段的数据类型;
3.对于方法而言,描述符用于描述字段的数据类型、参数列表、返回值。

描述符的表示方法:
1.基本数据类型用大写字母表示,如int,用I表示。
2.对象类型用“L对象类型的全限定名”表示,如String name,用Ljava/lang/String表示。
3.数组用“[数组类型的全限定名”表示。如String[] arr,用[java/lang/String表示。
4.描述方法时,将参数根据上述规则放在()中,()右侧按照上述方法放置返回值。而且参数之间无需任何符号。如public String name(int i),用(I)Ljava/lang/String表示。


5.访问标志

常量池后面紧跟着访问标志(access_flags),一个u2的2个字节类型。
主要标志当前类或接口的访问信息。一共有16个标志位可以使用,
当前只定义了其中8个(JDK1.5增加后面3种),没有使用到的标志位一律为0。
在这里插入图片描述

6.类索引/父类索引/接口索引集合

  • 这三项数据主要用于确定这个类的继承关系。
  • 类索引this_class:占用一个u2类型,2个字节。
  • 父类索引super_class:占用2个字节,因为是单继承所以只会有一个索引值。
  • 接口索引interfaces
  • 因为接口是多实现,所以可能有多个接口
  • 接口索引计数器(interfaces_count):占用2个字节,如果没有接口则此处是0,后边的接口索引集合也不占用位置。
  • 接口索引集合(interfaces):一组u2类型数据的集合。用来描述这个类实现了哪些接口。
  • 它们中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量。也即均在常量池中能找到对应的值,注意他们的关系。

7.字段表集合

字段计数器(fields_count):不包含局部变量,只有成员变量加入计算范围。

字段表集合(fields):一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的局部变量。且不会显示父类继承到的字段。

1.标志位:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。这些都是boolean值,所以使用标志位比较合适。
2.字段数据类型:通过引用常量池的值表示。
3.字段名称:通过引用常量池的值表示。

注意:2和3的引用值都是指的指向常量池的Constant_fields_info类型的值,具体见常量池的类别图。


8.方发表集合

1.方发表计数器(methods_count):2个字节,方法的个数。
2.方法表集合(methods):结构同字段表集合一样。记录所有的本类中的方法,如果没有重写继承的父类的方法则不会显示在此。同样也会有一些编译器自动添加的方法,比如:类构造器和实例构造器方法。
3.方发表不同于字段表的就是方法内会有很多代码,那么编译后的代码跑哪去了呢?因为此处保存的只有标志位、方法名索引(指向常量池)、方法描述索引(指向常量池)。不要急,因为编译后的代码都存到了下边要讲的"属性表集合"中的"code"数据项中。


9.属性表集合

属性表集合是一个可以作用在class文件中的字段表、方法表中并携带相关属性的一个集合,用来扩展描述额外的属性信息,比如方发表中会携带属性表集合,用来描述方法内部的代码被编译后的字节码指令,这些内容将会保存在对应的属性表集合的code属性中。

属性表集合的各个属性:
在这里插入图片描述


10.字节码指令

此处简单了解,后续会详细讲解。

方法调用的指令:

  • 1.invokevirtual:该指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是 Java 语言中最常见的方法分派方式。这里说的其实就是调用虚方法,而虚方法就是体现了虚这个字,就是需要动态绑定的方法,即需要经过常量池解析后将符号引用转换成直接引用的方法,说白了就是可以被覆写的方法都可以称作虚方法,因此虚方法并不需要做特殊的声明,也可以理解为除了用private、static、final修饰之外的所有方法都是虚方法(构造方法也要除外)。
  • 2.invokeinterface:该指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
  • 3.invokespecial:该指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(就是构造方法)、私有方法和父类方法。
  • 4.invokestatic:该指令用于调用类方法(static 方法)。
  • 5.invokedynamic:该指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前4条调用指令的分派逻辑都固化在java虚拟机内部,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

调用时机:这些指令在线程要调用方法时就会被运行,将方法压入java栈的栈帧,并进行运算,最后将结果弹出栈帧。(暂时了解即可,后续讲栈、线程部分会详解)

方法调用解析:常量池中我们说过,class被编译后常量池中有大量的符号引用,而方法调用并不是方法执行,而是有一个解析的过程,也即确定方法调用哪个版本,这一过程就会将一部分符号引用替换成直接引用,这个步骤将包括调用private、static、final和构造方法的方法直接确认,因为他们在执行引擎执行调用指令时,会先进行解析,此时就会确定调用版本,但是不包含虚方法哦。

方法调用分派:此过程就会涉及到调用虚方法,即虚方法的调用怎么确认版本和调用目标的,也即继承关系、多态的解释。这部分后续章节详解。

同步指令(synchronized语义):

  • monitorenter:被同步语义作用的代码编译后会在代码前生成monitorenter指令,用来获取锁的语义。
  • monitorexit :编译后monitorexit将出现在代码后,用来确保退出锁。

其他指令大家可到《深入理解Java虚拟机原理》书中的附录查看,此处不一一列举。

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

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

相关文章

ChatGPT 帮我跑了一个完整的 DevOps 流水线,离了个大谱...

大家好,我是米开朗基杨。上篇文章给大家介绍了👉如何将 N 个 ChatGPT 账号接入微信,今天就来给大家演示一下如何利用 ChatGPT 帮我工作,让自己有更多的时间摸鱼!上篇文章还没看的赶紧去看👇我将 9 个 ChatG…

SpringBoot2核心技术(核心功能)- 05、Web开发【5.3 请求参数处理】

5.3、请求参数处理 0、请求映射 1、rest使用与原理 xxxMapping;Rest风格支持(使用HTTP请求方式动词来表示对资源的操作) 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户 现在&#xff1…

Google 释出开源软件漏洞扫描工具 OSV-Scanner​

开源开发人员可在项目使用 OSV-Scanner,透过比对依赖项目和 OSV 漏洞资料库,找出项目的依赖项目中所存在的漏洞。Google 推出免费工具 OSV-Scanner(https://github.com/google/osv-scanner),供开源开发人员可以更简单地…

docker网络模式 与 搭建nginx

目录 1. docker网络模式 2. 连接容器的三种方法 3. Docker Networking 3.1 创建网络 3.2 查看宿主机中创建的网络 3.3 删除网络 3.3 如何使用网络 4.搭建Nginx 1.准备工作 1.1 拉取镜像 1.2 在宿主机中创建挂载目录 2.准备2个tomcat 容器集群 3.准备 Nginx配置 3.…

ffmpeg 命令的简单使用

ffmpeg命令是在windows端使用的,使用前,需要先下载对应的 exe文件 1.准备环境 访问FFmpeg官网的下载地址(https://www.gyan.dev/ffmpeg/builds/)下载对应的压缩包,解压后即可使用 2.使用 ffmpeg.exe的使用 • 功能 …

利用Clion编译器完成C++的头文件与源文件的映射

1、前言 嘿嘿,众所周知,本人是一名Java后端人员,那么为什么开始搞C/C了咧? 因为Java是在C/C的基础上开发的语言,而且性能也是业界公认的除了机器语言外最好的编程语言,所以我就想啊,如果将Java…

记一次返工

记一次返工 作者:Grey 原文地址: 博客园:记一次返工 CSDN:记一次返工 本文搬运自自己的博客园博客,发布于 2018-05-12 说明 本周我经历了参加工作以来,最大的一次返工,这一周都是茶饭不思…

安全灵活,华为云桌面成为数字化办公最佳搭档

目前云上数字化办公已经是大势所趋,但是如何快速高效地为用户提供便捷高效的工作和生活体验,依然需要大量的技术投入来实现。而华为云桌面就是云上办公的门户与平台,它可以将各种业务系统在云端进行集中管理与调度,通过统一的接口…

二、collection接口

文章目录Collection接口和常用方法(以ArrayList为例)基本使用方法遍历元素方式1(iterator)遍历元素方式2(增强for)练习Collection接口和常用方法(以ArrayList为例) 以ArrayList,其他集合同理使用 基本使用方法 注:集合添加基本数据类型会自动装箱成对…

非零基础自学Golang 第15章 Go命令行工具 15.1 编译相关指令 15.1.2 run

非零基础自学Golang 文章目录非零基础自学Golang第15章 Go命令行工具15.1 编译相关指令15.1.2 run第15章 Go命令行工具 15.1 编译相关指令 15.1.2 run 我们在调试代码时通常会使用go run命令。 该命令会编译执行Go语言源码,不会在当前目录生成可执行文件&#x…

【LeetCode每日一题:1799. N 次操作后的最大分数和~~~记忆化搜索+动态规划+状态压缩+最大公约数】

题目描述 给你 nums ,它是一个大小为 2 * n 的正整数数组。你必须对这个数组执行 n 次操作。 在第 i 次操作时(操作编号从 1 开始),你需要: 选择两个元素 x 和 y 。 获得分数 i * gcd(x, y) 。 将 x 和 y 从 nums 中…

07. 渗透测试之针对网站的信息收集

07. 渗透测试之针对网站的信息收集 01 信息收集简介 什么是信息收集 信息收集(Information Gathering)是指通过各种方式获取所需要的信息。信息收集是信息得以利用的第一步,也是关键的一步。信息收集工作的好坏,会影响整个渗透…

全国各城市疫情达峰进度条感染高峰时间表最新

防疫政策放开之后,多位专家就研判,未来一个多月内全国疫情将达到感染高峰。而近日,一张全国各地疫情进度和最终高峰的预计时间表流传,对各城市首轮感染高峰期进行了预测。那么,全国各城市疫情达峰进度条如何了&#xf…

怎么高效的开发一款成功的产品?Working Backwards

过去的几天一直在回顾整个产品团队过去一年所做的工作,有的工作有亮点,有的工作可以说是乏善可陈。对于不好的,发现其中的一个核心原因就是没有坚持“以终为始”的原则。现将我2021年10月写的一篇公司内部博客再次分享给团队,也分…

Simple Yet Effective Graph Contrastive Learning for Recommendation

1. 摘要 图神经网络(GNN)是一种强大的基于图的推荐系统学习方法。最近,结合对比学习的gnn在处理高度稀疏数据时,在数据增强方案的推荐方面表现出了优异的性能。尽管它们取得了成功,但大多数现有的图对比学习方法要么在用户-项目交互图上执行随…

JAVA零基础小白学习教程之day08_接口多态

day08-JAVAOOP 课程目标 1. 【理解】什么是接口 2. 【掌握】接口的定义格式 3. 【掌握】接口的使用 4. 【理解】接口的成员特点 5. 【理解】类和接口 抽象类和接口之间的关系 6. 【掌握】单继承多实现 7. 【理解】接口之间的多继承 8. 【掌握】接口的案例 9. 【理解】什么是…

一个程序员的新冠防护最佳实践

至今未阳,做了几次抗原检测都是阴性,所以把个人的防护经验给广大程序员朋友分享一下,尤其家里有小孩老人的可以参考一下。 我一天的防护操作 1、午餐 吃午饭时,走楼梯不去挤电梯,而且是在其他人吃完饭后&#xff0c…

Unreal Engine中调试常用方法

目录 常用调试方法 AddOnScreenDebugMessage UE_LOG:在控制台看调试信息 在蓝图中直接调用PrintString 自定义日志分类 声明 定义 简化日志输出的宏 日志格式化输出 常用调试方法 在虚幻引擎中常用的打印日志方法有三种,分别是:UE_…

C++运算符重载,匿名对象

目录 1、加号运算符重载 1.1 通过自己写成员函数,实现两个对象相加属性后返回新的对象 1.2通过成员函数实现加法运算符重载 1.3通过全局函数实现加法运算符重载,运算符重载也可以发生函数重载 1.4总结--对于内置的数据类型的表达式运算符是不可以改变…

Python+Requests实现接口自动化测试

一般对于自动化的理解,有两种方式的自动化。 第一,不需要写代码,完全由工具实现,这种方式的工具一般是公司自己研发的,方便黑盒测试人员使用。这种工具的特点是学习成本低,方便使用,但是通用性…