走进Java中的class文件

前言

背景

  • 最近,笔者在研究源码时,突然对源码的底层实现产生了好奇。

  • 代码在底层的世界到底是怎么样的呢?

  • 为了揭盖它神秘的面纱,笔者踏上了浪漫的求学之路,并写下此文,以作记录。

温馨提醒

阅读本文,您至少需要有一定的 Java 语法基础

图解文本文件与二进制文件

温馨提醒:如果您有字符编码和二进制文件相关的计算机知识储备,或者如果您对这部分内容不感兴趣,可以选择跳过

文章推荐https://www.cnblogs.com/alionxd/articles/3151019.html

具体内容:如下所示

(1) 三层模型

  • 首先,我们引入一个模型,方便之后的讲解
  • 如下图所示,这个模型分三个层次,分别是"显示层"、“处理层”、“存储层”
  • “存储层”:表示计算机在底层存储的原始数据(用0和1表示)
  • “处理层”:表示原始数据被进行处理的过程
  • “显示层”:表示原始数据被处理后的结果
    在这里插入图片描述

(2) 数值的多种表示

  • 接下来,我们需要认识一个概念:同一个数值的不同表示
  • 对于某一个数值,在意义层面上只有一个,但是在表示层面上,可以有多种表达方式

在这里插入图片描述
在这里插入图片描述

  • 换个角度思考,这是不是也意味着,对于一个给定的数字符号,我们也许不能确定它的数值呢?
  • 比如,别人写了"11" 这个符号,你能确定它是数值多少吗?
  • 答案是否定的,严格来说,我们不能直接确定它是多少,因为我们并不知道它是采用什么进制
  • 如果它是二进制的,那么"10"则表示数值3
  • 如果它是八进制的,那么"10"则表示数值9
  • 如果它是十进制的,那么"10"则表示数值11
  • 如果它是十六进制的,那么"10"则表示数值17
  • 最后,我们能够理解一点就好:对于某一个数值来说,即使他有不同的形式,但是所代表的含义只有一个,那就是数值本身

(3) 表示二进制数据

  • 我们知道,计算机底层存储的数据都是二进制的形式,也就是存储 1010 1110 而不是存储 256174AE 这些符号
  • 从严谨性来说,我们在讨论和描述计算机底层的数据时,就应该使用 1010 1110
  • 但是,在人类世界中,我们总是直接描述 1010 1110 是很累的
  • 为了方便沟通和显示数据,我们往往使用 1010 1110 的"别名"——十六进制 AE去表示
  • 这就好比小明不会叫他的妻子为"古丽热巴·买买提",而是直接叫"老婆",反正本质都是指向同一个意思
  • 因此,以后我们在表达二进制数据时,不再使用二进制符号表示,而是使用十六进制符号表示,你只要不忘记它本质是0101的数据即可
  • 在充分理解了为什么我们用十六进制的 AE表示二进制数据 1010 1110 后,我们将模型表示为下图
    在这里插入图片描述

(4) 文本文件

  • 所有文件,都可大致分为两类。一类是文本文件,另一类则是二进制文件
  • 文本文件,通常用记事本等文本编辑器打开。在打开之后,里面显示的信息我们可以直接看懂
  • 我们知道,在计算机底层文件都是以0和1进行存储的
  • 要想将存储层中的二进制数据变成我们能看懂的符号,就需要对二进制数据进行处理,而这个处理的过程,我们称之为字符编码
  • 常见的编码规则有 ASCII、UTF-8等
  • 注意:下图中的二进制数据是随意乱写的,只是为了描述这个过程

在这里插入图片描述

  • 由上图不难发现,为什么当你用"ASCII 编码规则"写数据到计算机中后,再将这些数据用"UTF-8 规则"读取出来会出现乱码的现象
  • 文本文件的原理大致就是这样。对于其中各式各样的具体的编码规则,如果你感兴趣的话,可以自行深入了解

(5) 二进制文件

  • 接下来我们讨论二进制文件
  • 打个比喻,如果说文本文件是偏向用来展示的,花拳绣腿做表面功夫的,那么二进制文件,则是偏向实打实干活的,简单粗暴,干就完了!
  • 二进制文件是有数据类型的,不同类型的数据类型占的字节大小不太一样
  • 二进制文件通常用于保存数值,至于这些数值是什么含义就看它的设计者所给的定义了,当然,二进制文件也能保存字符型的数据(也就是和文本文件效果一样)
  • 另外,大端模式和小端模式是针对数据类型的存储顺序的讨论,而不是针对字符串而言的,字符串没有所谓的大小端模式,只是按照固定顺序进行解析
  • 下面,列举一个例子来帮助我们理解

在这里插入图片描述

  • 从上图可以知道,为什么我们在使用文本编辑器工具打开二进制文件时,部分数据出现乱码现象,而部分数据没有乱码
  • 同时我们也能看到,使用二进制文件存储数值比较节约空间
  • 一般来说,二进制文件里面存储的数值是给计算机看的,是让计算机进行运算的,又或者这些数值有其他含义和作用。虽然这些二进制数据很原始、很简单粗暴,但这恰恰符合了它的特点:这些数据本来就不是为了给我们看的;而是给某些程序和代码的,给计算机处理的。所以,我们通常都不会用文本编辑器去打开二进制文件
  • 综上所述,在面对一个二进制文件时,我们不能准确地知道它的含义,这很正常。如果真的好奇里面的数据表达什么含义或执行什么功能,我们就需要获得这些数据储存方式的说明书。说明书会告诉我们从第几个字节到第几个字节是什么类型的数据,储存的数据是什么含义。

JVM 与 Class 文件

  • 代码编写完成后,需要转换成计算机 CPU 可以理解和执行的二进制机器码。这是因为 CPU 只能直接执行以 0 和 1 表示的二进制指令
  • 但是,对于像 Java 这样的编程语言,它们并不是直接编译成特定 CPU 的机器码,而是编译成一种中间格式的文件,即 Class 文件(字节码文件)
  • 这种 Class 文件是一种平台无关的格式,它可以在任何安装了相应虚拟机的操作系统上运行。虚拟机(如 Java 虚拟机 JVM)负责将 Class 文件中的指令转换成特定操作系统的机器码
  • 虚拟机的作用是屏蔽不同操作系统和 CPU 指令集的差异,使得 Java 等语言编写的程序可以在不同的硬件和操作系统上运行,而无需为每种平台单独编译。这就是所谓的“一次编写,到处运行”

在这里插入图片描述

  • 此外,Java 虚拟机的设计者在设计之初就考虑并实现了其它语言在 Java 虚拟机上运行的可能性
  • 所以并不是只有 Java 语言能够跑在 Java 虚拟机上,时至今日诸如 Kotlin、Groovy、Jython、JRuby 等一大批 JVM 语言都能够在 Java 虚拟机上运行
  • 它们和 Java 语言一样都会被编译器编译成字节码文件,然后由虚拟机来执行

在这里插入图片描述

Class 文件结构

(1) 整体认识

  • 我们通过一个例子来开始
  • 这里有一个 Student.java 的文件,里面的内容如下图所示

在这里插入图片描述

  • Student.java 文件进行编译,生成 Student.class 文件。然后,我们通过二进制文件查看工具打开它

在这里插入图片描述

  • 我们可以看到,Class 文件由一堆的二进制数据组成,以1字节为基础单位
  • 现在这一堆0和1看起来很底层,查看数据起来不太方便,我们现在将这些二进制数据用十六进制的符号表示,如下图

在这里插入图片描述

  • 好了,这下看起来舒服多了~~,我们继续…
  • 可以看到,Class 文件里面的数据严格按照顺序紧凑的排列,中间无任何分隔符,这使得整个 Class 文件中存储的内容几乎全部都是程序运行的必要数据,极大的精简了空间
  • 那么,这些二进制数据到底表示什么意思呢?
  • 首先,对于这些二进制数据,JVM 当然非常明白它是什么意思,但我们普通人就不知道了
  • 所以接下来的目的,就是让我们一起认识这些二进制数据,看看它们表达了什么意思
  • 我们还是先通过图片,去直观地认识一下吧
    在这里插入图片描述
  • Class 文件内容的含义,严格按照顺序进行解析,包含以下几点
    1. 魔数
    2. 版本号
    3. 常量池
    4. 访问标记
    5. 索引(类索引、父类索引、接口索引)
    6. 字段表集合
    7. 方法表集合
  • 接下来,我们将对这些内容逐个进行解析

(2) 具体解析

魔数

  • 如图所示,红色方框所圈住的四个字节就是魔数

在这里插入图片描述

  • 当 JVM 扫描这四个字节后,它就明白当前文件是 class 文件
  • 具体而言,JVM 会在验证阶段检查文件开头是否以该魔数开头,如果不是,则会抛出 ClassFormatError 错误
  • 不知道你发现没?这四个字节用十六进制表示的话,是 CA FE BA BE,恰好与英文单词 cafe babe (咖啡宝贝) 长的一模一样。我们回想 Java 的图标,它是一杯热气腾腾的咖啡,由此可见,里面真的妙不可言啊,咖啡!好像也挺不错呢嘻嘻~

在这里插入图片描述

版本号

  • 紧跟着魔数后面的四个字节表示版本号,其中前面两个字节表示副版本号,后面两个字节表示主版本号

在这里插入图片描述

  • 十六进制 00 00 转换成十进制是 0,十六进制 00 34 转换成十进制是 52
  • 所以我们可以知道,当前这个class文件采用的 Java 技术是版本:52.0(主版本号.副版本号)
  • 我们到网上一查,很轻易就能知道,字节码的版本号为52.0的,对应的就是 Java 8
  • 如果 Java 7 版本的 JVM 读取当前 class 文件,当它读到版本号信息时,就会抛出异常,JVM 无法继续加载该class文件(原因:低版本的 JVM 不能理解 class 文件里面所出现的高级语法,因此无法执行)
  • 如果是 Java 8 及以上版本的 JVM 读取当前 class 文件,则没有什么问题

常量池

  • 紧跟在版本号之后的是常量池(红色方框所圈住的部分)

在这里插入图片描述

  • 常量池最开始的两个字节 00 18 的十进制是 24,表示当前常量池里面有24 - 1 = 23 个常量表;也就是说,第0个常量表我们不用管(也管不着),我们是从第1个常量表开始,一直到第23个常量表,对应的是索引1~23
  • 所以当我们看到常量池最开始的两个字节为 00 18 也就是十进制 24 的时候,我们心里就要清楚,接下来的一连串二进制数据对应的是23个常量表

在这里插入图片描述

  • 我们可以看到:常量池,本质上就是一堆的二进制数据;而常量表,本质上就是其中连续的一小段二进制数据

  • 常量池就相当于一个用来存放资源的大型仓库,里面存放着一个又一个的常量表;而常量表,就相当于一个小仓库,里面的东西就是真正的数据。可以说,常量表是常量池读取有效信息的基本单位

  • 细心的你应该发现了,一个个常量表所占的字节数是有可能不一样的。既然如此?那到底我该如何判断哪些字节组合才是一个常量表呢?常量表里面的数据又表示着什么信息呢?

  • 带着这些疑问,我们一起去认识一下常量表吧

  • 常量表一共有11种,这些表的区分方法是:表结构的第一位都是类型为 u1 称为 tag 的值,根据 tag 值的不同来区分当前是哪一种表;对应到二进制数据中就是,每个表的第一个字节表示的是该表的种类

  • 下表列出的是11种常量表的简单说明(简单浏览一下就可以了,以后需要用的时候再来这里查表)

tag 值(大白话:种类)描述类型所属类别
1UTF-8编码的字符串CONSTANT_Utf8_info字面量
3整型字面量CONSTANT_Integer_info字面量
4浮点型字面量CONSTANT_Float_info字面量
5长整型字面量CONSTANT_Long_info字面量
6双精度浮点型字面量CONSTANT_Double_info字面量
7类或接口的符号引用CONSTANT_Class_info符号引用
8字符串类型字面量CONSTANT_String_info字面量
9字段的符号引用CONSTANT_Fieldref_info符号引用
10类中方法的符号引用CONSTANT_Methodref_info符号引用
11接口中方法的符号引用CONSTANT_InterfaceMethodref_info符号引用
12字段或方法的部分符号引用CONSTANT_NameAndType_info符号引用
  • 下表展示的是11种常量表的具体结构(简单浏览一下就可以了,以后需要用的时候再来这里查表)
  • 补充知识u1u2u4u8 分别表示 (占1个字节的)无符号数(占2个字节的)无符号数(占4个字节的)无符号数(占8个字节的)无符号数
  1. CONSTANT_Utf8_info
类型u1u2u1
项目taglengthbytes
说明1UTF-8编码的字符串总共占用的字节数UTF-8编码的字符码值
  1. CONSTANT_Integer_info
类型u1u4
项目tagbytes
说明3整型常量值
  1. CONSTANT_Float_info
类型u1u4
项目tagbytes
说明4单精度浮点型常量值
  1. CONSTANT_Long_info
类型u1u4u4
项目taghigh_byteslow_bytes
说明5长整型的高四位值长整型的低四位值
  1. CONSTANT_Double_info
类型u1u4u4
项目taghigh_byteslow_bytes
说明6双精度浮点的高四位值双精度浮点的低四位值
  1. CONSTANT_Class_info
类型u1u2
项目tagname_index
说明7存储constant_pool中的索引值,CONSTANT_Utf8_info类型
  1. CONSTANT_String_info
类型u1u2
项目tagstring_index
说明8存储constant_pool中的索引值,CONSTANT_Utf8_info类型
  1. CONSTANT_Fieldref_info
类型u1u2u2
项目tagclass_indexname_and_type_index
说明9存储constant_pool中的索引值,CONSTANT_Class_info类型。记录定义该字段的类或接口constant_pool中的索引值,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)
  1. CONSTANT_Methodref_info
类型u1u2u2
项目tagclass_indexname_and_type_index
说明10存储constant_pool中的索引值,CONSTANT_Class_info类型。记录定义该字段的类或接口constant_pool中的索引值,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)
  1. CONSTANT_InterfaceMethodref_info
类型u1u2u2
项目tagclass_indexname_and_type_index
说明11存储constant_pool中的索引值,CONSTANT_Class_info类型。记录定义该字段的类或接口constant_pool中的索引值,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)
  1. CONSTANT_NameAndType_info
类型u1u2u2
项目tagname_indexname_index
说明12constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的描述符
  • 通过简单浏览上面的表,我们对常量表的种类和结构应该有了初步的认知和了解。为了进一步认识,我们继续上面的例子

在这里插入图片描述

  • 常量池最开始的两个字节 00 18 十进制是 24,表示当前常量池有有23个常量表,紧接着出现的字节是 0A,它的十进制是 10,表示tag=10,我们去查 tag 为 10 的表,发现它是 CONSTANT_Methodref_info 表,它的结构是 [u1、u2、u2](1字节、2字节、2字节),结合表的具体信息,我们不难得出下面的结论
  • 0A 00 02 00 03 整体表示的是 CONSTANT_Methodref_info 这个常量表
  • 其中的 0A 的十进制是 10,表示它是表的类型是 tag=10
  • 00 02 的十进制是 2,表示类或接口为 #2(#2的意思是:让我们转去常量池的第2个常量表里面找具体数据)
  • 至于 00 03 ,它的十进制是 3,表示类或接口中的字段名和字段描述符为 #3(#3的意思是:让我们转去常量池的第3个常量表里面找具体数据)

在这里插入图片描述

  • 接下来,我们将一口气解析好几个常量表,以帮助我们真正快速的理解它的含义

  • 为了更方便的解释其中含义,对于这些二进制数据,我们将不再采用十六进制进行表示,我们现在采用十进制进行表示

十六进制表示法

在这里插入图片描述

十进制表示法

在这里插入图片描述

  • 解析结果如下图所示

在这里插入图片描述

  • 在常量池里面,以 001 开头,并且后面紧跟着2个字节表示字符串占用的字节个数的,其后面的二进制数据是字符串信息,它们采用UTF-8进行编码

在这里插入图片描述

  • 为了更方便的查看里面的数据,这里我们使用一款 IDEA 的插件—— jclasslib
  • 将源代码进行编译之后,会生成 .class 文件,我们用 jclasslib 插件打开它,它会自动解析文件里面的二进制数据并以可读的方式展示给我们看

在这里插入图片描述

  • jclasslib 解析出来的内容,和我们讲述的完全一致,是不是感觉非常方便?

在这里插入图片描述

在这里插入图片描述

  • 至此,我们已经对常量池有着较为深刻的认识了

访问标记

  • 在常量池结束之后,紧接着的两个字节代表访问标记(access_flag)

在这里插入图片描述

  • 这个标记用于识别一些类或者接口层次的访问信息,包括:这个 .class 文件是类还是接口,它是否定义为 public 类型,它是否定义为 abstract 类型;如果它是类的话,有没有声明 final 等等
  • 常用的标记如下表所示
标记值 (0x00)标记名称描述
00 01ACC_PUBLICpublic 类型
00 10ACC_FINALfinal 类型
00 20ACC_SUPER调用父类的方法时,使用 invokespecial 指令
02 00ACC_INTERFACE接口类型
04 00ACC_ABSTRACT抽象类型
10 00ACC_SYNTHETIC标记为编译器自动生成的类
20 00ACC_ANNOTATION标记为注解类
40 00ACC_ENUM标记为枚举类
80 00ACC_MODULE标记为模块类
  • 注意标记是可以进行组合的,比如 publicfinal 组合的标记值是 00 11
  • 本例中是 00 21,也就是使用了 public 00 01 和默认的 (super) 00 20

在这里插入图片描述

在这里插入图片描述

索引

  • 访问标记之后紧接着的是索引,包含三个部分:类索引、父类索引、接口索引。这三部分用来确定类的继承关系
  • 下图中的 00 07 是类索引,00 02 是父类索引,00 00 是接口索引(00 00 表示这个 .class 文件没有接口索引)

在这里插入图片描述

在这里插入图片描述

字段表集合

  • 在索引之后,就到 字段表集合
  • 字段表集合 格式为:字段表的总个数+字段表+字段表+...
  • 其中 字段表 格式为:字段访问标志+字段名索引+字段描述符索引+属性表集合
  • 属性表集合 格式为:属性表的总个数+属性表+属性表+...

字段表的结构

类型u2u2u2~
项目access_flagname_indexdescriptor_index~
描述字段访问标记字段名索引字段类型(描述符)索引属性表集合

字段访问标记

标记值 (0x00)标记名称含义
00 01ACC_PUBLIC字段为public
00 02ACC_PRIVATE字段为private
00 04ACC_PROTECTED字段为protected
00 08ACC_STATIC字段为static
00 10ACC_FINAL字段为final
00 40ACC_VOLATILE字段为volatile
00 80ACC_TRANSIENT字段为transient
10 00ACC_SYNCHETIC字段为synchetic
40 00ACC_ENUM字段为enum

属性表的通用结构

对于每一个属性表,它的属性名称都要从常量池中引入一个 CONSTANT_UTF8_info 类型的常量来表示,而属性表后半部分的结构,则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的字节数

类型u2u4~
项目attribute_name_indexattribute_lengthinfo
描述属性名称的索引属性值的长度属性值
  • 在上面,我们列出了在分析 字段表集合 过程中所有可能要用到的结构信息。接下来,我们结合这些信息,继续分析class文件的数据
  • 一个类中定义的字段会被存储在字段表中,包括静态的和非静态的
  • 参考上面的格式信息可知,下图最开始的两个字节 00 01 表示字段表的总个数。这里 00 01 的十进制是 1,也就是总共有 1 个字段表

在这里插入图片描述

  • 在知道了总共有 1 个字段表后,就到了分析具体的字段表了,我们先看前面 6 个字节 00 1900 0900 0A

在这里插入图片描述

  • 在上图中,00 19 是关于字段访问标记的信息。参考字段访问标记的表格可知,00 1900 0100 0800 10 组合而成,也就是该字段的访问标记是 **public static final **
  • 00 09 表示该字段名索引。00 09 十进制是 9,说明该字段名称的索引为 9,即:要想找该字段的名称,就需要去常量池第 9 个常量表 (#9) 里面找
  • 00 0A 的十进制是 10,表示该字段类型(描述符) 在 #10 的位置。即:要想知道该字段的类型,就需要去常量池的第 10 个常量表里面找
  • 我们可以借助 jclasslib 插件验证上述说法是否正确

在这里插入图片描述

  • 由格式 字段访问标志+字段名索引+字段描述符索引+属性表集合 可知,接下来我们分析的是:该字段的属性表集合

  • 属性表集合 = 属性表的总个数+属性表+属性表+...

  • 由下图可知 00 01 表示该字段拥有的属性表个数。由于 00 01 的十进制是 1,所有该字段只有 1 个属性表

在这里插入图片描述

  • 紧接着我们分析具体的属性表
  • 参考属性表的通用结构可知,下图中的 00 0B 00 00 00 02 00 0C 应该划分成 00 0B00 00 00 0200 0C

在这里插入图片描述

  • 00 0B 十进制是 11 ,表示要去常量池 #11 找具体的属性名称,这里我们使用 jclasslib 去查看这个属性名称是什么,如下图所示

在这里插入图片描述

  • 00 00 00 02 十进制是 2,表示属性值长度为 2,即:接下来的 2 个字节是属性值
  • 00 0C 的十进制是 12,表示属性值为 12,意味着要去常量池 #12 查找具体的值,我们到 jclasslib 找找看,这个值最终是什么?

在这里插入图片描述

  • 截至目前为止,我们对整个"字段表集合 "的学习就基本结束了
  • 但是,这里还有一个重点内容需要补充
  • 我们把注意力集中到下图中的 “描述符” 那里

在这里插入图片描述

  • “描述符”,就是类型的意思。图片里面显示它的值为 #10,意味着要我们去常量池的第10个常量表里面找。但是,你看旁边红色的符号,是不是显示 <I>,上面说过,这个是预执行的值,就是帮我直接把 #10 位置的字符直接显示给我们看,方便我们学习,而不用大费周章自己去查找常量池里面的第10个常量表
  • 我们这里是学习阶段,所以我们还是亲自去常量池里面找找看,顺便验证一下上面的说法是否正确

在这里插入图片描述

  • 亲自查找过后,我们确认上面的说法是正确的,也就是说,字段 aaa 的描述符(类型) 是 I。类型 I 是什么意思呢?
  • 对于基本数据类型,我们用一个字符来表示,比如说 I 对应的是 intB 对应的是 byte
  • 对于引用数据类型,我们用 L***; 的格式来表示,比如说一个经典的引用数据类型——字符串,它是 Ljava/lang/String;
  • 对于数组来说,我们用 [ 符号放在开头,比如说字符串数组是 [Ljava/lang/String;
  • 至此,我们完美结束字段表集合的学习

方法表

  • 字段表集合之后,就是方法表集合

  • 与字段表集合类似,方法表集合也是采用一样的套路

  • 方法表集合 格式为:方法表的总个数+方法表+方法表+...

  • 其中 方法表 格式为:方法访问标志+方法名索引+方法描述符索引+属性表集合

  • 属性表集合 格式为:属性表的总个数+属性表+属性表+...

方法表的结构

类型u2u2u2~
项目access_flagsname_indexdescriptor_index~
描述方法访问标记方法名索引方法类型(描述符)索引属性表集合

方法访问标记

标记值 (0x00)标记名称含义
00 01ACC_PUBLIC方法为public
00 02ACC_PRIVATE方法为private
00 04ACC_PROTECTED方法为protected
00 08ACC_STATIC方法为static
00 10ACC_FINAL方法为final
00 20ACC_SYNCHRONIZED方法为synchronized
00 40ACC_BRIDGE方法是由编译器产生的桥接方法
00 80ACC_VARARGS字段接受不定参数
01 00ACC_NATIVE字段为native
04 00ACC_ABSTRACT方法为abstract
08 00ACC_STRICTFP方法为strictfp
10 00ACC_SYNTHETIC方法是由编译器自动产生的

属性表的通用结构

对于每一个属性表,它的属性名称都要从常量池中引入一个 CONSTANT_UTF8_info 类型的常量来表示,而属性表后半部分的结构,则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的字节数

类型u2u4~
项目attribute_name_indexattribute_lengthinfo
描述属性名称的索引属性值的长度属性值
  • 在上面,我们列出了在分析 方法表集合 过程中所有可能要用到的结构信息。接下来,我们结合这些信息,继续分析class文件的数据

  • 方法表用来存储方法的信息,包括方法的修饰符,方法名称,方法参数。

    方法访问标记,用于描述方法的修饰符;

    方法名索引,指向常量池中的某一项,用于描述方法的名字;

    方法描述符索引,指向常量池中的某一项,用于描述方法的参数和返回值类型。

  • 参考格式信息可知,下图最开始的两个字节,表示方法表的总个数

  • 很明显,这里 00 02 的十进制是 2,也就是总共有 2 个方法表

在这里插入图片描述

  • 接下来,我们进入到第 1 个方法表进行具体分析
  • 还记得方法表的格式吗?是的,就是:方法访问标志+方法名索引+方法描述符索引+属性表集合
  • 我们先看 方法访问标志方法名索引方法描述符索引
  • 如下图,00 01 表示方法的访问标记是 public ;而 00 05 十进制是 5,表示方法名在常量池的 #5 位置;对于 00 06 十进制是 6,表示方法描述符(也就是方法类型)在常量池 #6 位置

在这里插入图片描述

  • 我们也可以在 jclasslib 插件里面查看信息,检验一下是否正确

在这里插入图片描述

  • 现在,我们来看该方法的 属性表集合
  • 回忆一下:属性表集合 格式为:属性表的总个数+属性表+属性表+...
  • 所以,下图中的 00 01 十进制是 1,表示总共有 1 个属性表

在这里插入图片描述

  • 查找属性表的通用格式可知

    00 0D 十进制是 13,表示属性名称的索引为 13(在常量池 #13 位置)

    00 00 00 2F 十进制是 47,表示属性值的长度为 47

    而橙色标记的 47 个字节数据,表示具体的属性值

在这里插入图片描述

  • 我们通过 jclasslib 去查看这些数据到底是什么意思,如下图所示
  • 很明显,属性名索引为 #13,其指向的内容是 “Code”
  • 然后,这个 Code 属性的属性值长度是 47
  • 具体的属性值就是"字节码"红色方框圈住的内容。这些内容,实际上就是方法体里面的源代码在经过编译器编译后,变成的字节码指令。我们通常会查看这些字节码指令,去判断源代码执行效率的高低!

在这里插入图片描述

  • 至此,我们就分析完了第 1 个方法表
  • 同理,第二个方法表也是这样进行分析,这里我们就快速分析一下吧

在这里插入图片描述

  • 结合结构信息分析上图:

    00 09 表示方法访问标记由 00 0100 08 组合而成,也就是 public static

    00 12 的十进制是 18,表示方法名索引为 #18

    00 13 的十进制是 19,表示方法类型(描述符)索引为 #19

    00 01 的十进制是 1,表示方法总共有 1 个属性表

    00 0D 的十进制是 13,表示属性名索引为 #13

    00 00 00 2B 的十进制是 43,表示属性值的长度是 43 个字节

    橙色标记的 43 个字节数据,表示具体的属性值

  • 我们到 jclasslib 插件查看详细内容

在这里插入图片描述

在这里插入图片描述

  • 至此,我们就快速分析完了第 2 个方法表
  • 方法表集合的学习就告一段落了

属性表集合

  • 属性表集合分为三种:

    第一种属性表集合,是字段表里面的属性表集合

    第二种属性表集合,是方法表里面的属性表集合

    前面的这两种,我们在上面都学习过

    而本节要讲述的内容,就是第三种属性表集合

  • 第三种属性表集合,它是紧跟着方法表集合之后的,主要的用途就是描述 class 文件所携带的一些辅助信息,例如:该 class 文件的名称等等

  • 属性表集合的格式,和属性表的格式,都是通用的,这里我们就直接快速讲解了,不再详细分析

在这里插入图片描述

  • 如上如所示,00 01 的十进制是 1,表示属性表集合总共有 1 个属性表;00 16 的十进制是 22,表示属性名索引为 #2200 00 00 02 的十进制是 2,表示属性值的长度占 2 个字节;00 17 表示具体的属性值(00 17 的十进制是 23)
  • 我们借助 jclasslib 查看里面具体内容

在这里插入图片描述

  • 至此,我们完美结束

总结

(1) class 文件内容顺序总体上和 Java 源文件内容顺序一致

(2) class 文件绝大部分信息,是保存在常量池中

(3) 在未来,我们通常会使用 jclasslib 或类似的工具来帮助我们快速查看 class 文件内容,而不是自己去对照组 JVM 规范翻译数据内容

至此,全文结束!感谢您的观看,希望能帮助您。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值