class字节码文件中的常量池结构详解
文章目录前言方法区常量池基本结构JVM 所定义的11种常量常量池元素的复合结构常量池的结束位置常量池元素总数量第一个常量池元素父类常量变量型常量池元素自己的学习笔记,部分节选自《揭秘java虚拟机》前言对于一个class文件,内容有:以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数这里主要说class文件中的常量池:constant_pool_count常量池计数器
自己的学习笔记,部分节选自《揭秘java虚拟机》
前言
对于一个class文件,内容有:
以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数
这里主要说class文件中的常量池:
constant_pool_count
常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。constant_pool 表的索引值只有在大于 0 且小于 constant_pool_count 时才会被认为是有效的 ,对于 long 和 double 类型有例外情况。
注意:虽然值为 0 的 constant_pool 索引是无效的,但其他用到常量池的数据结构可以使用索引 0 来表示“不引用任何一个常量池项”的意思。
constant_pool[ ]
常量池,constant_pool 是一种表结构,它包含 Class 文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池中的每一项都具备相同的格式特征——第一个字节作为类型标记用于识别该项是哪种类型的常量,称为“tagbyte”。常量池的索引范围是 1 至 constant_pool_count−1。
Jdk1.6及之前:有永久代, 常量池在方法区
Jdk1.7:有永久代,但已经逐步“去永久代”,常量池在堆
Jdk1.8及之后: 无永久代,常量池在元空间
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量:
·被模块导出或者开放的包(Package)
·类和接口的全限定名(Fully Qualified Name)
·字段的名称和描述符(Descriptor)
·方法的名称和描述符
·方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
·动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
方法区
既然提到常量池就顺便提一下方法区,方法区主要保存的信息是类的元数据。方法区与堆空间类似,它也是被JVM中所有的线程共享的区域。方法区中最为重要的是类的类型信息、常量池、域信息、方法信息。类型信息包括类的完整名称、父类的完整名称、类型修饰符(public/protected/private)和类型的直接接口类表。
常量池基本结构
Java类所对应的常量池主要由常量池数量和常量池数组两部分组成(如下图所示),常量池数量紧跟在次版本号的后面,占2字节。常量池数组则紧跟在常量池数量之后。 常量池数组,顾名思义,就是一个类似数组的结构。这个数组固化在字节码文件中,由多个元素组成。与一般数组概念不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度也是不同的,但是每一种元素的第一个数据都是一个u1类型,该字节是标志位,占1个字节(如图4.4所示)。JVM解析常量池时,根据这个u1类型来获取该元素的具体类型。常量池的结构组成如图所示。
使用结构化的方式来描述常量池数组的编排,可以如下描述: tag1元素内容1 tag2元素内容2 tag3元素内容3 … tagn元素内容n 这里为了描述,在tag与元素内容之间添加了空格,但实际的class 二进制文件中,这些tag与元素内容之间绝没有任何空格信息,也没有任何其他的多余信息,tag后面紧跟着元素内容,第一个元素内容结束了,紧接着就是第二个元素的tag信息,由此可见,整个字节码文件的格式设计都是非常紧凑的。
JVM 所定义的11种常量
常量池元素中的不同元素结构与类型都是不同的,正因如此,JVM只能定义有限的元素类型,并针对有限的类型进行专门解析。JVM一共定义了11种常量,
JVM常量池元素一览表
常量池元素的复合结构
常量池数组中的每一种元素的内容都是复合数据结构的,下面分别给出JVM所定义的常量池中每一种元素的具体结构。 该表中tag值为1的常量池元素CONSTANT_Utf8_info,其组成结构为3部分,分别是:tag、length和bytes,其中tag和length的长度分别是u1、u2,即分别占1字节和2字节。而bytes则是字符串的具体内容,其长度是length字节。在字节码文件中,该常量池元素最终所占的字节数是: 1 + 2 + length 其他类型的常量池元素的组成结构类似。
常量池元素结构:
可以看到,类的方法信息、接口和继承信息、属性信息都是定义在NamedAndType_Info中的。
常量池的结束位置
相信有不少读者读到这里,可能潜意识里会突然蹦出这么一个问题:整个字节码文件由多个部分构成,常量池数组只是其中一块,JVM在解析字节码文件时,一定需要分别读取各个部分的字节流,其中也包括常量池数组。但是常量池中有部分元素的值是bytes数组,其长度是随机变化的,那么JVM在解析时,是如何知道整个常量池的信息解析到什么位置结束呢?其实经过分析不难发现,首先,class文件给出了常量池的总数;其次,凡是碰到有bytes数组的常量池元素,class文件在常量池的每一个元素之前都会专门划分出2字节用于描述该常量池元素内容所占的字节长度,这样一来,常量池中每一个元素的长度是确定的,而常量池的总数也是确定的,JVM据此便可以从class文件中准确地计算出常量池结构体的末端位置(起始位置不用计算,是定死的,从第9字节开始,前面8字节分别是魔数和版本号)。
常量池元素总数量
前面8字节用于描述魔数和版本号,从第9字节开始的一大段字节流都用于描述常量池数组信息。其中,第9和第10字节用于描述常量池元素的总数量。
第9和第10字节所保存的常量池数组大小是0x33,换算成十进制是51,说明该字节码文件中一共包含51个常量池元素。JVM规定,不使用第0个元素,因此实际上一共有50个常量池元素(下文在解析源码时,会看到源码中的确是从第1个元素开始解析的,而不是从第0个)。
第一个常量池元素
常量池数量之后(即从第11字节开始),就是常量池数组。每一个常量池元素都以tag位标开始,tag位标都只占1字节长度。如图所示。
第11字节对应的值是7,对照上文所给的常量池11种元素的复合结构可知,tag位标为7代表的是CONSTANT_Class_info,即类或接口的符号引用,这种类型的元素的结构组成如下:
◎ tag位标 占1字节
◎ index 占2字节
既然tag位标已经占据了字节码文件的第11字节,则接下来的第12和13字节将合起来表示index。如上图所示,这两个字节对应的值为2。
从第11字节开始,到第13字节为止,一个常量池元素就被描述完成。紧接着开始描述第2个常量池元素
第一个常量池元素后面紧跟着的是第二个常量池元素。其第一字节是01,表示这是一个UTF8编码的字符串,其结构是:
◎ tag位,占1字节
◎ length,占2字节
◎ bytes,占length字节
如图所示,tag位后面的2字节的值是4,表示bytes占4字节,其值为0x54657374,其中每两字节正好代表一个字符,对应的字符串是Test。
*
父类常量
刚才的第1与第2两个常量用于描述Java类型信息,接下来的第3与第4两个常量则用于描述父类信息。由于Test.Java类并没有显式继承任何类,因此编译后处理成默认继承,即父类是Java.lang.Object。
父类常量的tag位也是07,其类名是java/lang/Object
下面分别给出第3和第4这两个常量在文件中的内容。
图4.9显示,字符串的length值为0x10,即16,于是其后面的16字节都是bytes。由此可以进一步验证,字符串常量的结构由tag、length和bytes组成,bytes的长度由length指定。
变量型常量池元素
常量池中前面4个元素主要用于描述Java类自身的名称和其父类名称,接下来的字节码流则开始描述类中的变量信息,这些变量既包括类的成员变量,也包括类变量(即静态变量)信息。 首先看类成员变量a的信息
图所选中的8字节,一共包含两个常量池元素信息,这两个常量池元素的类型都是字符串,因为其tag位都是1。第一个字符串常量的length是1,其值(即bytes)是0x61,正好对应UTF-8编码的字符a。第二个字符串常量的length也是1,其值是0x49,正好对应UTF-8编码的字符I。在JVM规范中,若变量的类型是I,则表示该变量的实际类型是int。这与上文对变量a的定义一致。 接着看类变量si的定义,
所选中的27字节,一共描述了两个常量池元素,这两个常量池元素的类型也都是字符串。第一个字符串的length为2,其值是0x7369,对应utf-8编码的字符串si。第二个字符串的length为0x13,即19,其值是0x4C 6A 61 76 61 2F 6C 61 6E 67 2F 49 6E 74 65 67 65 72 3E,这一串值是ASCII字符,每2个十六进制数对应一个ASCII字符,这些数字连起来就对应一个字符串,所对应的字符串是Ljava/lang/Integer;。 这两个常量池元素合起来,描述了Test类中的static Integer si这样的类变量。
更多推荐
所有评论(0)