根据前文 jvm之字节码解读——认识字节码 ,我们可知,java的字节码文件结构为
前文已经对魔数和版本号进行了分析
jvm之字节码解读——认识字节码文件结构解析(魔数、版本号)
下面,对紧跟在版本号的字段——Constant Pool(常量池)进行分析
关于常量池,它的作用是,在jvm执行方法或者GC等动作时,jvm可以直接从常量池拿到数据,进行直接操作(因为有些数据会重复去拿,常量池的存在就使得整个字节码文件变得更小)
我们先把之前的源代码和用javap命令得到的内容贴出来
package com.bill;
public class MyByteCodeTest1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
Classfile /E:/dc_workspace/test/target/classes/com/bill/MyByteCodeTest1.class
Last modified 2021-3-12; size 487 bytes
MD5 checksum 54fc0467aba1376b708775659da99343
Compiled from "MyByteCodeTest1.java"
public class com.bill.MyByteCodeTest1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // com/bill/MyByteCodeTest1.a:I
#3 = Class #22 // com/bill/MyByteCodeTest1
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/bill/MyByteCodeTest1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyByteCodeTest1.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/bill/MyByteCodeTest1
#23 = Utf8 java/lang/Object
{
public com.bill.MyByteCodeTest1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/bill/MyByteCodeTest1;
public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/bill/MyByteCodeTest1;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 12: 0
line 13: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/bill/MyByteCodeTest1;
0 6 1 a I
}
SourceFile: "MyByteCodeTest1.java"
大家看到下面代码段里,有个“Constant pool”,里面的内容就是常量池的内容了
从这里看到,常量池有23个
从字节码文件结构可知,常量池字节码长度是2+n个字节,并且,前2个字节是常量池的个数,由字节码数据可知
常量池的个数为0x0018个,即十进制的24个
但是,我们看到,从javap命令得到的常量池个数是23个,原来,jvm预留了第0个常量池的位置,记为null,是为了兼容后续有可能用到null这个东西
要理清楚常量池,必须搭配另一个表
这个表是什么意思?这里常量池里可以理解成常量数组,每个数组的大小都是动态的,其中,数组里每个元素,又是不同的结构体,但是这些结构体第一个元素一定是标记位(tag)
ps1:这里U1表示读取1个字节,U2表示读取2个直接
ps2:这里注意一下,大家会看到,这个表里,会有2个数据类型——index和bytes,这里index表示的是这个值是个偏移量,比如值是1,意思是具体的值其实是常量池里第1个常量;如果是bytes,则表示这里的就是这个值本身
下面以这个例子为例,讲解常量池
首先,我们读取一个直接,作为tag
读取到的数值是0x0A,即十进制的10,我们查常量池数据类型结构表发现10对应的结构是这样的
这个结构说明什么?
说明了这个常量是一个方法的信息,占据5个字节,后面还有4个字节,2个字节是另一个常量A在常量池的索引+2个字节是再一个常量B在常量池的索引(因为类型都是index)
在这里是
这里的意思就是,前2个字节0x0004表示CONSTANT_Class_info(其实就是这个方法对应的类是哪个),这个0x0004表示的就是在常量池排第4的那个常量,至于具体值是多少,jvm会直接去读
而后2个字节是0x0014(十进制20)表示的是CONSTANT_NameAndType_info,表示的是这个方法对应的名字、出入参,表示的意思常量池第20个常量
有人会说,这么去看的话,具体这些东西,是不是只有全部常量池分析出来,才能知道分别代表的具体意思是啥(因为你现在根本不知道第4号常量和第20号元素是啥)
理论上说,是。但是,上面说了,我们看一下我们用javap命令执行的返回内容,里面是不是有个“Constant pool”,我们看一下有什么
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // com/bill/MyByteCodeTest1.a:I
#3 = Class #22 // com/bill/MyByteCodeTest1
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/bill/MyByteCodeTest1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyByteCodeTest1.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/bill/MyByteCodeTest1
#23 = Utf8 java/lang/Object
看到了吗?这里已经把常量池所有的常量信息都分析出来了
看一下我们分析的第一个常量,我们是不是得出结论,里面的4个字节代表的是2个指针,一个是指向4号常量,另一个指向20号常量,其实这里已经帮我们列出来了
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
那这里4号和20号常量分别是什么呢?
#4 = Class #23 // java/lang/Object
#20 = NameAndType #7:#8 // "<init>":()V
通过这里的内容,我们一下子就能找到相关信息了,其实在这里,1号常量表示的是Object类的构造方法
到此为止,常量池里的第一个常量就分析完毕了。
我们再分析多一个常量,下一个常量的tag是什么呢?
通过字节码文件,我们读到的是0x09,再查询常量池数据类型结构表,查9对应的那一栏
意思就是这个常量占1+2+2=5个字节,下一个常量又得从5个字节之后获取
具体的分析过程跟上面类似,就不再赘述了
那么,jvm怎么知道常量池分析到哪里为止呢?
我们前面提过,常量池的个数其实是确定的(这里是24个),jvm分析的时候会累加,当jvm发现常量池个数已经达到24了,就不会继续按照常量池的分析规则去分析字节码,而转向用jvm的文件结构,去分析常量池下一个元素(即Access Flag)的标准来解析字节码文件
在这里,常量池常量表示到的最后是
后续的篇章,我们会继续以这里为起点,分析字节码文件剩下的内容