走进Java中的class文件

Java中的Class类 Class类的作用 Class类是一个特殊类,它用于表示JVM运行时类或接口的信息。 Class类提供很多方法用于获取类的各种信息,比如获取类名、判断该类是否是一个接口还是普通类等等。 在Java中枚举类是一种类,而注解是一个接口,数组也是一个类;Java原始类型(boolean, byte, char, short, int, long, float, and double)和关键字void也被表示为Class的对象。 你知道吗? 在每一个基本类型的包装类中都有一个共有的静态变量,例如Integer: 阅读详情

前言

背景

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

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

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

温馨提醒

阅读本文,您至少需要有一定的 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 而不是存储 256、174、AE 这些符号
  • 从严谨性来说,我们在讨论和描述计算机底层的数据时,就应该使用 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种常量表的具体结构(简单浏览一下就可以了,以后需要用的时候再来这里查表)
  • 补充知识:u1、u2、u4、u8 分别表示 (占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标记为模块类
  • 注意标记是可以进行组合的,比如 public 和 final 组合的标记值是 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 19、00 09、00 0A

在这里插入图片描述

  • 在上图中,00 19 是关于字段访问标记的信息。参考字段访问标记的表格可知,00 19 由 00 01、00 08、00 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 0B 和 00 00 00 02 和 00 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 对应的是 int,B 对应的是 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 01 和 00 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,表示属性名索引为 #22;00 00 00 02 的十进制是 2,表示属性值的长度占 2 个字节;00 17 表示具体的属性值(00 17 的十进制是 23)
  • 我们借助 jclasslib 查看里面具体内容

在这里插入图片描述

  • 至此,我们完美结束

总结

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

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

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

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

【Java】查看class文件的jdk编译版本的两种方式 使用EditPlus打开该class文件,字符集选择16进制(Hex viewer)。我们只需要看major version的值是多少(它的值是十进制数字)。仅看第一行数据,前面8个字节CA FE BA BE是固定的。次版本后面的4个字节00 34 就是jdk版本。cmd进入class文件所在目录后,运行以下命令。之后4个字节00 00 是次版本。 阅读详情

相关推荐

Java-Class<?>

结合反射是非常有用的,尤其是在你需要处理不确定类型的类信息时。当你不知道具体的类型时,可以使用 Class<?> 时,你无法访问到具体的类型信息,因为类型信息在编译时被擦除了。如果你需要处理具体类型的 Class 对象,你应该使用带具体类型的 Class<T>。> 时,你无法访问到具体的类型信息,因为类型信息在编译时被擦除了。如果你需要处理具体类型的 Class 对象,你应该使用带具体类型的 Class<T>。如果你需要处理具体类型的 Class 对象,你应该使用带具体类型的 Class<T>。

兴趣是最好的老师 1626

java class类详解

这种方式在编译时就会受到检查(因此不需要置于try语句块中),不仅可以应用于普通的类,也可以应用于接口、数组及基本数据类型。时会判断类是否已经加载,没有加载则会调用类加载器来加载类,已经加载过了,不会重复加载。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良。当程序创建第一个对类的静态成员的引用时,就会加载这个类。如果尚未加载,默认的类加载器就会根据类的全限定名查找。创建类对象的时候也会被当作对类的静态成员的引用。代码块只会在第一次加载类的时候被调用,由输出结果得知,

jacklin_01的博客 6088

Pixhawk变砖自救指南:从DFU模式到固件重刷全流程(附2.4.8实测)

本文详细介绍了Pixhawk飞控变砖后的自救方法,从DFU模式进入、固件重刷到高级恢复技巧全流程。特别针对国产版本boot0引脚未引出的特殊情况提供了解决方案,帮助用户通过QGC或Mission Planner地面站工具恢复飞控功能,适用于2.4.8等常见固件版本。

weixin_29168813的博客 337

【Java】Java Class对象的理解

JAVA Class对象的理解、泛型的理解

sinat_29846389的博客 1万+

SAP公司间采购后台配置及前台操作详解

公司间采购后台配置及前台操作详解 SAP 实施中公司间交易几乎每个项目都会有此业务需求,比如内地很多公司都会在香港设置贸易型子公司。通过香港公司接单,然后转内地公司生产,或内地公司通过香港公司采购进口原材料,这些业务必然会涉及到公司间交易,下面我将详细说明下SAP是如何实现这种业务场景的,供大家参考。 配置前必须前台先创建公司间供应商主数据和客户主数据 1. 创建相应的客户主数据 功能说明 公司间...

weixin_40672823的博客 1万+

ZYNQ裸机LWIP双网口实现

文章目录前言一、LWIP双网口二、使用步骤1.部分说明2.部分代码总结 前言 一、LWIP双网口 由于项目需要多通讯口,查阅资料发现lwip支持多网口操作。于是,就开搞了。 二、使用步骤 1.部分说明 利用xemac_add函数在网卡链表中添加一个网卡,即使用netif块重复xemac_add操作。 先用轮询的方式实现双网口,后期修改为中断模式 2.部分代码 导入官方例程LWip echo server, 修改main函数,增加一个netif块 #if LWIP_IPV6==0 #if L..

weixin_35865280的博客 1万+

java一个文件两个class_一个Java文件中可以定义多个Class么?

一个Java文件可以定义多个Class么 ?答案肯定是可以的。下面我们测试一下://Test.javaclass A {}class B {}class C {}class D {}创建一个Test.java文件, 在这个文件中定义了4个类 分别是A,B,C,D。然后在编译一下这个Test.java文件,看会发生什么?[boardMacBook-Pro ~]$ javac Text.java[bo...

weixin_35988178的博客 6050

华为OD机考双机位C卷 - 文件存储系统的排序(Java & Python & JS & GO & C++ & C)

华为OD机考双机位C卷 - 文件存储系统的排序, 提供Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。

算法大师! 779

【Java JVM】Class 文件

类或接口的符号引用表 CONSTANT_Class_info 的内容如下。

LCN29的博客 1657

【数据集】3D-GloBFP:全球首个三维建筑轮廓数据集

【数据集】3D-GloBFP:全球首个三维建筑轮廓数据集

WW、forever的博客 1680

Android16 rk3576添加触摸点数上限到40个点

本文介绍了在Android 16系统上实现40点触摸支持的修改方案。相比Android 15,Android 16对API添加有更严格的要求,需要在MotionEvent.java中添加@FlaggedApi批注。主要修改包括:1)在input_framework.aconfig中定义flag;2)在MotionEvent.java中添加40个AXIS_GENERIC轴常量并添加批注;3)将32位指针ID处理改为64位适配;4)更新相关API文档。修改涉及多个核心文件,包括PointerController

tzj1058756809的博客 381

Java中的class文件结构

Java中的.class文件结构是Java源代码经过编译后生成的二进制文件结构,它包含了Java类的结构和信息。以下是对Java中.class文件结构的详细解析综上所述,Java中的.class文件结构是一个复杂但有序的二进制流,它包含了Java类的结构和信息。通过对.class文件结构的解析,我们可以了解Java类的内部结构、字段、方法以及它们之间的关系等信息。

sutingStart的博客 1063

E2B Agent Runtime Sandbox 沙箱技术架构研究

本实现指南基于E2B的技术架构,提供了完整的基于Firecracker和Docker的Agent Runtime Sandbox后端服务实现方案。架构设计:参考E2B的分层架构,实现API层、管理层和虚拟化层Firecracker集成:深度集成Firecracker提供硬件级安全隔离完整功能:支持文件读写、shell执行、浏览器访问等核心功能生产就绪:提供Docker部署、监控、测试和安全配置可扩展性:设计支持水平扩展和高并发场景。

AI天才研究院 744

如何将class文件反编译成java文件

多少不凡、只因不甘。 工作中最常遇见的是查看某些想查看某些依赖源码时,又不能下载源码,往往只能通过提供的 jar 文件反编译查看。 一般情况下如果只是查看,通常有两种方式: 通过 idea 编辑器引入mvn依赖可直接查看 jar 中的文件,如果是class文件可直接拖入idea编辑器中就可查看 下载 JD-GUI 编辑器(是一个class文件反编译java文件软件),可直接打开 jar 文件、class文件、class文件目录 JD-GUI 具体软件如下图样式 很特殊的情况下,如果.

木枫 3万+

大模型知识边界探索:LLM设计的极限测试

最优化理论是数学中的一个分支,主要研究如何从给定的一组约束条件下找到最优解。目标函数:定义了需要优化的指标,可以是最大化或最小化。约束条件:定义了在优化过程中需要满足的限制条件。无约束优化:目标函数和约束条件都不存在。有约束优化:目标函数存在,但存在约束条件。多目标优化:同时需要优化多个目标函数。极限测试(Ultimate Test)是一种系统性的方法,用于评估大型模型(如LLM)在极端条件下的性能和稳定性。极端数据分布:与模型训练数据分布显著不同的数据集。极端计算资源。

AI天才研究院 432

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

java替换jar中的class文件

将1步骤中编译好的class文件上传到服务器上,上传目录为2中获取的路径。覆盖原始的class文件。将需要修改的class文件代码复制到java编译工具中,代码改好以后编译成class文件。在调整java代码过程中会遇到需要改jar包中的class文件的情况,改了如何替换呢?在服务器上执行,注意是在jar目录中执行解压。重新把替换后的class文件压缩到jar中。如果需要替换多个class文件,命令改成即可。在linux服务器上执行。

whandgdh的博客 4943

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

在java中class是什么意思_java中的class是什么意思

java中的class代表类,class类存在于java.lang包中。在Java中,每个class都有一个相应的Class对象。当我们编写一个类并进行编译后,在生成的【.class】文件中就会产生一个Class对象,它用于表示这个类的类型信息。Class类(在java.lang包中,Instances of the class Classrepresent classes and interfa...

weixin_36410316的博客 1万+

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Java 利用反射 使用.class文件中的类

Java源文件编译之后会产生一个.class的文件,如果我们只有.class文件,而没有.java的时候,我们也可以使用这个类。 首先我们看一下这个工程的结构: 其他的地方可以忽略,只需要要知道,image包里面原来有Buddy.java编译过后产生了Buddy.class文件,接下来我们保留.class文件,删除.java文件。 接下来,我们用代码来调用.class文件中的方法。

Android 5551

java中的class文件

java是一次编译多次运行,是因为java中的JVM的作用,使用Java编译器可以把java代码编译为存储字节码的class文件。使用JRuby等其他语言编写,java虚拟机一样可以把程序代码编译成class文件,所有实现语言无关性的基础是虚拟机和字节码存储格式。虚拟机并不关心class文件的来源是什么语言,只要它符合class文件应有的结构就可以在java虚拟机中运行。 java语言中的各种变

u011393781的博客 998

Java-初识 .class 文件_class文件怎么打开

Java .class 文件解析与打开指南(2025版) .class文件是Java源代码编译后生成的字节码文件,采用二进制格式,包含JVM可执行的指令和元数据。本文详细介绍了其结构特征(如魔数0xCAFEBABE、常量池)和多种打开方式: 基础工具:javap命令可查看字节码指令 反编译工具:JD-GUI、CFR等可将.class还原为Java代码 高级分析:十六进制编辑器或ASM框架可深入分析字节码 文中还涵盖.class文件的生成过程、平台无关性特点,以及安全混淆等进阶内容,为开发者提供从基础认知到专

likuoelie的博客 2649

带你走进JVM之Class类文件

Class类文件结构 我们都知道java程序经过javac xxx.java编译后会生成xxx.class文件,接下来就要解析这个class类文件。 首先它的结构是什么呢? 1.Class文件是一组以8个字节为基础单位的二进制流(可能是磁盘文件,也可能是类加载器直接生成的),各个数据项目严格按照顺序- 紧凑地排列,中间没有任何分隔符。 2.Class文件格式采用一种类似于C语言结构体的伪结构来存储...

博客园 506

一个java文件有多个类,编译后有几个class文件

说明:这是一个新的系列,定下一个目标,只要不忙,每天都会进行一次更新 验证 public class ClassD { ClassB b; public static void main(String[] args) { System.out.println("xxx"); } class ClassE { ClassB b; } } class ClassF { ClassB b; } class ClassG { ClassB b; } abstract class Class

Dave_NewStar的博客 9588

java中class文件的意义在哪里

一、什么是.class文件 java的编译器在编译java类文件时,会将原有的文本文件(.java)翻译成二进制的字节码,并将这些字节码存储在.class文件。 也就是说java类文件中的属性、方法,以及类中的常量信息,都会被分别存储在.class文件中。当然还会添加一个公有的静态常量属性.class,这个属性记录了类的相关信息,即类型信息,是Class类的一个实例 二、class文

子衿_青青的博客 1万+

java文件编译为class文件需要键入什么命令_运行java的class文件方法详解

一、运行class文件执行带main方法的class文件,命令行为:java 注意:CLASS文件名不要带文件后缀.class例如:java Test如果执行的class文件是带包的,即在类文件中使用了:package 那应该在包的基路径下执行,命令行为:java .CLASS文件名例如:PackageTest.java中,其包名为:com.ee2ee.test,对应的语句为:package co...

weixin_34072003的博客 7666

java文件转为class文件在线工具

我整理的一些关于【Java】的项目学习资料(附讲解~~)和大家一起分享、学习一下:https://d.51cto.com/f2PFnNJava文件转为Class文件在线工具的科普 Java是一种广泛使用的编程语言,因其跨平台特性而受到开发者的青睐。在Java编程中,源代码文件通常以 .java 为扩展名。而编译后产生的字...

weixin_40488152的博客 2272

java怎么生成class文件_怎样将java的class文件生成jar包

首先,我们必须在电脑上安装JDK(java 开发工具包)。我们可以在官网上进行下载。2安装完成后,我们需要配置环境变量。我们将JDK的安装目录下的bin文件所在路径配置到环境变量的path变量中。3配置完成后,我们打开命令提示符窗口,输入“javac”,会提示如图所示信息。说明我们的java环境变量配置成功。4在D盘的java目录下创建一个haha.java文件(其他位置也是可以的)。并输入如下代...

weixin_29959491的博客 9652
上一篇: 随笔——重温Java基础语法
下一篇: Ajax 与 Vue 框架应用点——随笔谈
code_stream
博客等级 码龄4年 1238粉丝 169原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值