前言:前段时间读《深入java虚拟机》介绍到class文件的时候,由于理论知识较多,人总感觉疲惫不堪,就泛泛阅读了一下。在工作中使用起来知识点知道,但是总是需要查阅各种资料。今天有时间,继续整理常量池后面的相关知识。
文章目录
- 1、访问标志
- 2、类索引、父类索引、接口索引
- 字段表集合
- 1、字段表结构
- 2、字段表的访问标识
- 方法表集合
- 1、方法表结构
- 2、方法表的访问标识
Last modified 2023-6-2; size 708 bytesMD5 checksum fc8bb4833223a10b68449d42080b1695Compiled from "Sample.java"
public class com.company.jvm.Sampleminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref #6.#29 // java/lang/Object."<init>":()V#2 = Class #30 // com/company/jvm/Sample#3 = Methodref #2.#29 // com/company/jvm/Sample."<init>":()V#4 = String #31 // 22#5 = Fieldref #2.#32 // com/company/jvm/Sample.m1:Ljava/lang/String;#6 = Class #33 // java/lang/Object#7 = Fieldref #2.#34 // com/company/jvm/Sample.arr:[Ljava/lang/Object;#8 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;#9 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V#10 = Utf8 m1#11 = Utf8 Ljava/lang/String;#12 = Utf8 m2#13 = Utf8 arr#14 = Utf8 [Ljava/lang/Object;#15 = Utf8 <init>#16 = Utf8 ()V#17 = Utf8 Code#18 = Utf8 LineNumberTable#19 = Utf8 LocalVariableTable#20 = Utf8 this#21 = Utf8 Lcom/company/jvm/Sample;#22 = Utf8 main#23 = Utf8 ([Ljava/lang/String;)V#24 = Utf8 args#25 = Utf8 [Ljava/lang/String;#26 = Utf8 sample#27 = Utf8 SourceFile#28 = Utf8 Sample.java#29 = NameAndType #15:#16 // "<init>":()V#30 = Utf8 com/company/jvm/Sample#31 = Utf8 22#32 = NameAndType #10:#11 // m1:Ljava/lang/String;#33 = Utf8 java/lang/Object#34 = NameAndType #13:#14 // arr:[Ljava/lang/Object;#35 = Class #39 // java/lang/System#36 = NameAndType #40:#41 // out:Ljava/io/PrintStream;#37 = Class #42 // java/io/PrintStream#38 = NameAndType #43:#44 // println:(Ljava/lang/String;)V#39 = Utf8 java/lang/System#40 = Utf8 out#41 = Utf8 Ljava/io/PrintStream;#42 = Utf8 java/io/PrintStream#43 = Utf8 println#44 = Utf8 (Ljava/lang/String;)V
{public java.lang.String m1;descriptor: Ljava/lang/String;flags: ACC_PUBLICpublic java.lang.String m2;descriptor: Ljava/lang/String;flags: ACC_PUBLICpublic java.lang.Object[] arr;descriptor: [Ljava/lang/Object;flags: ACC_PUBLICpublic com.company.jvm.Sample();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/company/jvm/Sample;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new #2 // class com/company/jvm/Sample3: dup4: invokespecial #3 // Method "<init>":()V7: astore_18: aload_19: ldc #4 // String 2211: putfield #5 // Field m1:Ljava/lang/String;14: aload_115: bipush 1217: anewarray #6 // class java/lang/Object20: putfield #7 // Field arr:[Ljava/lang/Object;23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;26: aload_127: getfield #5 // Field m1:Ljava/lang/String;30: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V33: returnLineNumberTable:line 9: 0line 10: 8line 11: 14line 12: 23line 13: 33LocalVariableTable:Start Length Slot Name Signature0 34 0 args [Ljava/lang/String;8 26 1 sample Lcom/company/jvm/Sample;
}
SourceFile: "Sample.java"
1、访问标志
class文件中,我们可以通过背或记也好,或者通过查阅对照表。可以将常量池中的数据整理出来。常量池的数据,之后又是什么呢?紧接着的就是访问标志。
标志名称 | 标志值 | 标志意义 |
---|---|---|
ACC_PUBLIC | 0X0001 | 是否为public类型 |
ACC_FINAL | 0X0010 | 是否被声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 在jdk1.0.2之后编译出来的类的这个标志都为真 |
ACC_INTERFACE | 0x0200 | 标识是一个接口 |
ACC_ABSTRACT | 0x4000 | 是否为abstract类型,如为真,其他类型均为假,如INTERFACE |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户产生 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
ACC_MODULE | 0x8000 | 标识这是一个模块 |
这里先上一张二进制的图:
下面是常量池之后的class文件截取的部分:
00 21
访问标志,占用空间:U2,这里占用了十六进制的4个位,则是0x0021。这里我啰嗦一下,一个字节为8个位,对应到十六机制来说【两个位】代表一个字节。eg:0xF标识二进制的0000 1111,去除高位的0,就是1111。
在表格中我们说过,ACC_SUPER标志在jdk1.0.2之后的版本其值都为真,则是0x0020,说明其访问标志ACC_PUBLIC为真!0x0001|0x0020=0x0021
2、类索引、父类索引、接口索引
访问标志结束之后,就来到了我们所声明的类例,如下伪代码
public class dog extend cat implement animal{}
00 02 00 06 00 00
__类索引、父类索引、接口索引占用的内存均为u2。
u2 | 索引 | 说明 |
---|---|---|
00 02 | #2 | 代表当前类的索引,通过查找为com/company/jvm/Sample |
00 08 | #8 | 代表当前父类索引,通过查找为Object |
00 00 | 0 | 代表当前文件没有接口 |
字段表集合
通过字面意,就能得知这里将要介绍的是类或接口成员字段。
//这里写个伪代码
public final static int AGE=10;
1、字段表结构
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | filed_count | 1 | 字段数量 |
u2 | access_flag | 1 | 访问标志 |
u2 | name_index | 1 | 名称索引 |
u2 | descriptor_index | 1 | 类型索引 |
u2 | attribute_count | 1 | 属性计数器 |
u2 | attributes | attribute_count | 属性值集合 |
2、字段表的访问标识
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否为公开 |
ACC_PRIVATE | 0x0002 | 字段是否为私有 |
ACC_PROTECTED | 0x0004 | 字段是否为保护 |
ACC_STATIC | 0x0008 | 字段是否为静态 |
ACC_FINAL | 0x0010 | 字段是否为Final |
ACC_VOLATILE | 0x0040 | 字段是否在并发时可见 |
ACC_TRANSIENT | 0x0080 | 字段是否序列化 |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自己决定 |
ACC_ENUM | 0x4000 | 字段是否为枚举 |
00 03 00 01 00 0a 00 0b 00 00 00 01 00 0c 00 0b 00 00 00 01 00 0d 00 0e 00 00
位值 | 说明 |
---|---|
第一个成员字段 | |
00 03 | 代表字段数量有3个 |
00 01 | 代表字段访问标志位public |
00 0a | 代表名称索引为10 名称为 m1 |
00 0b | 代表descriptor的索引值为11 对象类型为Ljava/lang/String;【分号;全限定名】 |
00 00 | 代表没有属性数量 |
第二个成员字段 | |
00 01 | 代表字段访问标志位为public |
00 0c | 代表字段名称索引为12,名称为m2 |
00 0b | 代表类型的索引11,类型为 Ljava/lang/String;同上 |
00 00 | 代表没有属性 |
第三个成员字段 | |
00 01 | 同上 |
00 0d | 名称索引为13,经查找为arr |
00 0e | 类型索引为14,经查找为 [Ljava/lang/Object;全限定名,其中“[”代表为数组 |
00 00 | 代表没有属性,数量为0 |
方法表集合
唠嗑时间开始,写到这里花了三个多小时。从排版到书写上面确实有很大的提升。此时的我确实有点疲惫。仔细一想,没啥子疲惫不疲惫的,路虽远,但始终在路上,总会到达终点。突然想到书上说过这样的一句:当你在解决一个问题的时候,你会感到很疲惫,这时候千万别放弃。因为大部分的人就此放弃了,而你还在路上行走。当你解决之后,你又比别人强了不少!
写到这里【字段表集合】之后,这里就会很轻松。这里再啰嗦一下,字段表分为:成员字段数量、字段名索引、字段类型索引、字段属性数量、字段属性集合。
1、方法表结构
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
method_count | 方法数量 | ||
u2 | access_flag | 1 | 访问标志 |
u2 | name_index | 1 | 名称索引 |
u2 | descriptor_index | 1 | 类型索引 |
u2 | attribute_count | 1 | 属性计数器 |
u2 | attributes | attribute_count | 属性值集合 |
~ | ~ | ~ | ~ |
2、方法表的访问标识
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 方法是否为公开 |
ACC_PRIVATE | 0x0002 | 方法是否为私有 |
ACC_PROTECTED | 0x0004 | 方法是否为保护 |
ACC_STATIC | 0x0008 | 方法是否为静态 |
ACC_FINAL | 0x0010 | 方法是否为Final |
ACC_SYNCHRONIZED | 0x0020 | 方法是否在并发时可见 |
ACC_BRIDGE | 0x0040 | 方法是不是由编译器产生的桥接方法 |
ACC_VARCHAR | 0x0080 | 方法是否接收不可定参数 |
ACC_NATIVE | 0x0100 | 方法是否为Native |
ACC_ABSTRACT | 0x0400 | 方法是否为abstract |
ACC_STRICT | 0x0800 | 方法是否为strictfp【修饰在接口和类,对精确率类型较高且跨平台的计算结果要求比较严格的清醒的话,建议使用该strictfp关键词。】 |
ACC_SYNTHETIC | 0x1000 | 方法是否由编译器自动产生 |
继续上方发表的字节码:
00 02 00 01 00 0f 00 10 00 01 00 11 00 00 00 2f 00 01 00 01 00 00
位码 | 说明 |
---|---|
00 02 | 方法数量为2 |
00 01 | 方法访问标识为0x0001,经查为public |
00 0f | 方法名索引为15,经查为 |
00 10 | 方法类型索引16,经查为()V |
00 01 | 说明属性表集合有1项属性 |
00 11 | 属性名称的索引值为17,对应的为Code |
~~未完待续 |