类文件结构的故事(二) 待我称王封你为后i 2022-02-25 08:27 193阅读 0赞 ### 文章目录 ### * * 读常量池的方法 * UTF-8编码的字符串表 * 访问标记 * 类索引、父类索引、接口索引集合 * 字段表集合 * 方法表集合 * * 插播: 为什么重载,不能根据方法返回值进行区别 * 属性表集合 -------------------- ## 读常量池的方法 ## 之前简单的说了下,如何读常量池里面的数据项。 现在举个例子,为你我加深下印象。 拿 `CONSTANT_Class_info` 这张表举例子。它的结构如下: <table> <thead> <tr> <th>类型</th> <th>名称</th> <th>数量</th> </tr> </thead> <tbody> <tr> <td>u1</td> <td>tag</td> <td>1</td> </tr> <tr> <td>u2</td> <td>name_index</td> <td>1</td> </tr> </tbody> </table> 第一个数据项是 `tag` 是标记位的意思,第二个数据项 `name_index` 是类或者接口的全限定名。它的值是个 `u2` 类型,换算成十进制,指的就是常量池中的第几个表。从 `1` 开始 计算, `0` 之前已经说过,被系统设置了。 -------------------- ## UTF-8编码的字符串表 ## 这个比较特别,常量池中所有的引用,最后都指向一张字符串表,但是不是指向同一张。值得单独说下。 `CONSTANT_Utf8_info` 的结构如下: <table> <thead> <tr> <th>类型</th> <th>名称</th> <th>数量</th> </tr> </thead> <tbody> <tr> <td>u1</td> <td>tag</td> <td>1</td> </tr> <tr> <td>u2</td> <td>length</td> <td>1</td> </tr> <tr> <td>u1</td> <td>bytes</td> <td>length</td> </tr> </tbody> </table> 第一个还是标记位;第二个是长度,表示从当前开始,往后的 `length` 个字节就是该表 表示的数据。 -------------------- ## 访问标记 ## 常量池结束以后,紧跟着的是 访问标记 ,占用两个字节,用来描述接口或者类的信息,比如,是否是 `abstract` ,是否被定义 `public` 还是 `private` ,如果是类,是否被修饰为 `final` ; 同样的书上给出可一个关系表,列出了对应的关系,比如 `ACC_PUBLIC` 对应的标志值为 `0x0001` ,表示是否为 `public` ;`ACC_FINAL` 对应的标志值为 `0x0010` ,表示是否是 `final` 类;`ACC_SUPER` 对应的标志值为 `0x0020` , `JDK1.2` 以后编译出来的类,这个标志就为真;其他的就不一一列举了,上面推荐的工具,直接显示了该类的类型; ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lvdW5neW91dGg_size_16_color_FFFFFF_t_70] 主要说下这个标志值是怎么计算的,首先有许多标志位,只有被用的标志位才会被设置为对应的标志值,没用到的,统统设置为 `0` ; 比如这里使用到了 `final,super,public`,因此,三个标志的标志值进行 `|` 运算:`0x0001 | 0x0020 | 0x0010 = 0x0031` 也就是图中工具直接算出来的 `0x0031` ; -------------------- ## 类索引、父类索引、接口索引集合 ## 类索引、父类索引,都是一个 `u2` 类型的数据项,接口索引集合,从名字上就可以看出是个集合,是一组 `u2` 类型数据的集合; `Class`文件,通过这三个数据项,进行确定继承关系; **类索引**,用于确定该类、接口的全限定名; **父类索引**,用于确定该类、接口的父类的全限定名,除了 `java.lang.Object` ,所有的 `Java` 类的父类索引都不可以为 `0` ; **接口索引集合**,用于描述该类实现了哪些接口|接口继承了哪些接口,这些接口按照 `implement、extend` 后面从左到右的顺序,排列到接口索引集合中; 类索引、父类索引,都是一个 `u2` 类型的数据项,都指向一个 `CONSTANT_Class_info` ,然后 `CONSTANT_Class_info` 的索引指向一个 `CONSTANT_Utf8_info` ,里面保存着对应的 全限定名 ; 接口索引集合,由于是一个集合,所有跟常量池一样,首先有个入口,是一个 `u2` 类型的接口数量计数器,然后后面才是真正的集合;如果没有实现或继承任何接口,则计算器的值为 `0` ,后面接口的索引表将不再占用任何字节,也就是不存在; **都是保存着索引的,拿着索引去常量池里面寻找对应的数据;** -------------------- ## 字段表集合 ## 字段表用于描述接口或类中申明的变量; 这个变量是类中,不管是不是 `static` 修饰的,也就是类级别的、实例级别的 ,都算,但是方法内部的不算; 保存的信息,变量的修饰符、名字 ;其中修饰符,有数据类型修饰符(`int、douple...`),作用域修饰符(`private、public...`)、static、final、transient、volatile… ; 上面那些修饰符,除了数据类型修饰符,其他的修饰符,都是要么几选一,要么有,要么没有,都是布尔类型,很适合用一个标记位来表示;至于其他修饰符、类型是千奇百怪的,反正无法想到怎么用一个固定字节来表示,那我们就用字符串记录下,字符串放进常量池里面,这里仅保存引用 ; **字段表的入口,是一个 `u2` 字节的计数器;** **字段表结构如下:** <table> <thead> <tr> <th>名称</th> <th>类型</th> <th>数量</th> </tr> </thead> <tbody> <tr> <td><code>access_flags</code></td> <td><code>u2</code></td> <td>1</td> </tr> <tr> <td><code>name_index</code></td> <td><code>u2</code></td> <td>1</td> </tr> <tr> <td><code>descriptor_index</code></td> <td><code>u2</code></td> <td>1</td> </tr> <tr> <td><code>attributes_count</code></td> <td><code>u2</code></td> <td>1</td> </tr> <tr> <td><code>attributes</code></td> <td><code>attributes_info</code></td> <td><strong><code>attributes_count</code></strong></td> </tr> </tbody> </table> 除了数据类型的字段的修饰符放到 `access_flags` 和 **访问标记** 基本一样; `access_flags` 之后,就是 `name_index` 、`descriptor_index` ,都是索引,指向常量池; 1. `name_index` :简单名称,`int number();` 的简单名称就是 `number` ; 2. 类的 **全限定名** ,将 **类全名** 中的 `.` 换成 `/` ; 3. `descriptor_index`:描述符,字段的数据类型,方法的参数列表(顺序不可乱)和返回值,这些就是字段与方法的描述符; * 基本数据类型和 `void` 都用一个大写字母表示,对象类型使用 `L` 加上对象的全限定名表示 <table> <thead> <tr> <th>标识字符</th> <th>被标识的类型</th> </tr> </thead> <tbody> <tr> <td>B</td> <td>byte</td> </tr> <tr> <td>C</td> <td>char</td> </tr> <tr> <td>D</td> <td>double</td> </tr> <tr> <td>f</td> <td>float</td> </tr> <tr> <td>I</td> <td>int</td> </tr> <tr> <td>J</td> <td>long</td> </tr> <tr> <td>S</td> <td>short</td> </tr> <tr> <td>Z</td> <td>boolean</td> </tr> <tr> <td>V</td> <td>void</td> </tr> <tr> <td>L</td> <td>对象类型</td> </tr> </tbody> </table> 数组,什么类型的数组以及几维数组,就在对应的类型的标识字符前面添加对应的 `[`,比如 `int[][] number` 则记做 `[[I` ; 上面的例子只是描述字段,描述方法则按照以下顺序: 1. 参数列表;参数列表按照参数定义的顺序,放在一个 `()` 里面; 2. 返回值 ; 比如 `public String toString(char c,int[] number) ;` 的描述符为 `(C[I)Ljava/lang/String` ; **这之后就是额外信息描述计数器和额外信息描述,后面 属性表 那里再讲;字段的值就是放在那里面,我们这里说的都是字段的描述:修饰符、名字等等** 字段表集合不会列出父类的字段,但是可能列出一些不存在的字段,比如 **内部类的属性中可能会出现外部类引用** ,内部类可以直接访问外部类,那么外部类的可供内部类访问的字段,会被直接添加到内部类的字节码的字段表集合中的; -------------------- ## 方法表集合 ## 基本上和 **字段表集合** 一样; 就不再重复讲了; 主要说下,方法的修饰符、名字、描述符,都被保存起来了;那么方法体呢,被保存在哪里呢?方法体经过编译以后,变为字节码指令,存放在 **属性表** 集合中的 `Code` 属性里面 ; 同样父类的方法,如果没有被子类重写,是不会被放到该集合中的; 但是集合中可能出现一些编译器自己添加进去的方法,比如类构造器方法 `<clinit>` 和实例构造器方法 `<init>` ; -------------------- ### 插播: 为什么重载,不能根据方法返回值进行区别 ### `java` 虚拟机规范里面,指定 **方法特征签名**,仅仅包括了 方法名称、参数顺序以及类型 ; 各大厂商实现的虚拟机,都按照这个规范来的,在寻找方法的时候,仅仅按照方法特征签名来寻找的,而方法特征签名里面,没有返回值;因此,不能通过方法返回值进行区分重载的方法; 而在字节码文件里面,只要描述不是完全一致的方法,是可以共存的,也就是可以通过方法返回值进行区分,只是 `jvm规范` 中没有将方法返回值算进去,其实技术层面是可以通过返回值进行区分的 ; 因此,虚拟机平台上的其他语法在实现的时候,可以将这个纳入进去,转成字节码的时候 `JVM` 是认识的,但是 `java` 里面是无法做到了。 -------------------- ## 属性表集合 ## 属性表,和前面说的那些不太一样,它在好多地方都有出现,不是一个单独作为一项出现的,也就是它可以出现多次,不似之前的那些数据项只能在规定的地方出现一次。 前面讲的那些,某些数据项中存在属性表属性,其中有: `Class文件`、`字段表`、`方法表` ,这三个数据项都有自己的属性表,用于描述一些信息。 属性表本身也是一个表结构,其结构如下: <table> <thead> <tr> <th>类型</th> <th>名称</th> <th>数量</th> </tr> </thead> <tbody> <tr> <td>u2</td> <td>attribute_name_index</td> <td>1</td> </tr> <tr> <td>u4</td> <td>attribute_length</td> <td>1</td> </tr> <tr> <td>u1</td> <td>info</td> <td>attribute_length</td> </tr> </tbody> </table> 属性表的限制不像其他表和双规似的限制那么严格,它对其中的属性没有顺序要求,甚至对属性的个数也没有要求,任何人都可以往属性表里面添加属性,就是你添加的属性,`JVM` 不认识,在运行的时候会被自动忽略掉而已。 为了能正确的解析 `Class` 文件,解析里面的属性表的属性,`JVM` 规范目前制定了 `21` 个属性,用于描述特定场景的信息。**其中每个属性本身也是一张表。** 其中有些属性,只能用在 方法表或者字段表或者类文件中,也有可以三个通用的属性,也有可以用在其他属性的属性。一般是一些属性可以用在 `Code` 属性 。 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lvdW5neW91dGg_size_16_color_FFFFFF_t_70]: /images/20220225/7ad7a8f07f594dba8e690761f86c10aa.png
相关 类文件结构 1.Class类文件结构 Class文件是一组以8个字节为基础单位的二进制字节流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,Class文件格式 - 日理万妓/ 2024年02月22日 00:59/ 0 赞/ 107 阅读
相关 类文件结构 类文件结构 一 概述 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Ja 偏执的太偏执、/ 2023年05月30日 03:50/ 0 赞/ 16 阅读
相关 类文件结构 Class类文件结构 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。 ゝ一世哀愁。/ 2022年07月12日 05:19/ 0 赞/ 250 阅读
相关 类文件结构 Class类文件结构 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。 ╰半夏微凉°/ 2022年07月12日 05:19/ 0 赞/ 260 阅读
相关 Class类文件的结构 [jvm目录][jvm] -------------------- 我们知道我们编写的java代码只有编译成class文件之后才能被jvm虚拟机使用,不仅如此, 我会带着你远行/ 2022年05月11日 09:30/ 0 赞/ 313 阅读
相关 类文件结构 类文件结构 > 总结自周志明的《深入理解Java虚拟机》 概述 计算机只认识0和1,将我们编写的程序编译成二进制本地机器码已不再是唯一选择,越来越多的程序语言选择 ゞ 浴缸里的玫瑰/ 2022年05月03日 14:56/ 0 赞/ 269 阅读
相关 类文件结构 类文件结构 代码编译的结果从本机机器码转变为字节码,是存储格式发展的一小步,支是编程语言的一大步。 1. 概述 原来,程序需要编译成二进制本地机器码。类似C语言。 痛定思痛。/ 2022年03月21日 04:50/ 0 赞/ 323 阅读
相关 类文件结构的故事(二) 文章目录 读常量池的方法 UTF-8编码的字符串表 访问标记 类索引、父类索引、接口索引集合 字段表集 待我称王封你为后i/ 2022年02月25日 08:27/ 0 赞/ 194 阅读
相关 类文件结构的故事(三) > 周志明老师的《深入理解java虚拟机》 文章目录 Code 属性 -------------------- Code 属性 之前说到方发表的时 冷不防/ 2021年10月15日 09:45/ 0 赞/ 287 阅读
还没有评论,来说两句吧...