Jvm(35),class文件结构----常量池
Posted qingruihappy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jvm(35),class文件结构----常量池相关的知识,希望对你有一定的参考价值。
Class文件格式
常量池
先了解常量池中需要存放哪些内容,再讨论用什么类来存放这些内容。
常量池中存放的内容
Class文件中包含常量池,那么我就需要知道常量池会包含哪些内容,接下来才是关心class格式文件用什么类型来存放这些内容。
字面量(Literal)
字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为 final的常量值等。
符号引用(Symbolic References)符号引用则属于编译原理方面的概念,包括了下面三类常量:
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)方法的名称和描述符
其它:常量池中主要内容是上面2项,说明还有其它内容,这部分内容,在下面我们看到用来描述常量池内容的14种常量项的介绍时就发现标志为15、16、18的常量项类型是用来支持动态语言调用的(jdk1.7时才加入的)。
常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一。
常量池中为什么要包含这些内容
Java代码在进行Javac编译的时候,并不像C和C++那样有"连接"这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在
Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态连接的内容,在虚拟机类加载过程时再进行详细讲解。
Class文件中如何描述常量池中内容
知道Class文件的常量池包含的内容后,我们下面就来看看class格式文件使用了哪些类型数据来存放常量池的内容。
由Class文件格式可得紧接着主版本号的是常量池入口。
类型 | 名称 | 数量 |
u2(无符号数) | constant_pool_count | 1 |
cp_info(表) | constant_pool | constant_pool_count-1 |
占用的字节数:2+(constant_pool_count-1)个具体表所占字节。由上表可见,Class文件使用了一个前置的容量计数器
(constant_pool_count)加若干个连续的数据项(constant_pool)的
形式来描述常量池内容。我们把这一系列连续常量池数据称为常量池集合。
先给看一下TestClass.class文件全局的内容,下面就来分析其中常量池中的内容,其它内容后面的文章在分析。从图片也可以看出常量池内容占据了class文件的很大一部分,当然TestClass类中代码比较少就更显得常量池内容的多了。
package com.zlcook.clazz?
public class TestClass{ private int m?
public int inc(){
return m+1?
}
}
constant_pool_count
常量池容量计数值(u2类型):从1开始,表示常量池中有多少项常量。即constant_pool_count=1表示常量池中有0个常量项。
设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达"不引用任何一个常量池项目"的含义,这种情况就可以把索引值置为0来表示。Class 文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。
TestClass.class文件中constant_pool_count的十进制值为19,表示常量池中有18项常量,索引范围1-18。
constant_pool
constant_pool_count表明了后面有多少个常量项。
14种常量项结构
常量池中每一项常量都是一个表,JDK1.7之后共有14种不同的表结构数据。一个常量池中的每个常量项都逃不脱这14种结构。根据下图每个类型的描述我们也可以知道每个类型是用来描述常量池中哪些内容(主要是字面量、符号引用)的。比如:CONSTANT_Integer_info是用来描述常
量池中字面量信息的,而且只是整型字面量信息。而标志为15、16、18的常量项类型是用来支持动态语言调用的(jdk1.7时才加入的)。
常量池中的14种项目类型
这14种表(或者常量项结构)的共同点是:表开始的第一位是一个u1 类型的标志位(tag),代表当前这个常量项使用的是哪种表结构,即哪种常量类型。
这14种常量项结构还有一个特点是,其中13表占用得字节固定,只有
CONSTANT_Utf8_info占用字节不固定,其大小由length决定。为什么呢?因为从常量池存放的内容可知,其存放的是字面量和符号引用,最终这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,比如你定义一个类,类名可以取长取短,所以在没编译前,无法确定大小不固定,编译后,通过utf-8编码,就可以知道其长度。
表 | 占用字节 |
CONSTANT_Class_info | 3 |
CONSTANT_Integer_info | 5 |
CONSTANT_Fieldref_info | 5 |
CONSTANT_Methodref_info | 5 |
查找testClass.class文件的第一个常量项内容
由上面constant_pool_count得到值为19,因为从1开始计数,所以说明后面有18个常量项,由于每个常量项的表结构都不同但是第一位相同,所以读到第一位就可以确定表结构了。下面我们就来查看第一个常量项包含得内容,至于其它17个常量项内容类似,最后还会介绍java提供得一个工具命令javap来帮我们分析class文件字节码内容。
第一个表的tag为10
由上图可知常量池中第一项常量标志的16进制值是0x0A=10,查表发现这个常量属于CONSTANT_Methodref_info类型,此类型表示类中方法的符号引用。查看该类型的结构如下:
CONSTANT_Methodref_info类型结构
CONSTANT_Methodref_info型常量的第二个数据项为index,类型是u2,index存储的是一个索引值,从class文件中查得该值为 oX0004=4,即它指向常量池中第4个常量;第三个数据项也是索引其值为
0X000F=15,指向常量池种第15个常量。
Paste_Image.png
到此为止,第一个常量项是CONSTANT_Methodref_info型常量项,该类型常量项用来表示类中方法的符号引用,其内容为
tag=10,index1=4,index2=15,因为其表示的是类中方法的符号引用,所以index中存放的不是一个具体得内容,而是一个索引位置,所以说其具体内容存放在另一个常量项中。下面我们就来看看其索引指向的常量项
(即第4个常量项)的内容到底是什么?
找第4个常量项之前需要知道第4个常量项的开始位置,所以需要知道前3个常量项所占字节数。那好就看第2个常量项,由于第一个常量项共占了5个字节,则紧接着的字节就为第二个常量项的tag,如下图可得其值为 0X09=9,说明第2个常量项得项目类型为CONSTANT_Fieldref_info。查表得其该类型得字节长度固定占5个字节。
第二个常量项
依次类推查的第3,4个常量项为CONSTANT_Class_info型。如下图:
前4个常量项
下面就看第四个常量项CONSTANT_Class_info的内容0X070012。
CONSTANT_Class_info存放的是指向类或接口的符号引用。
CONSTANT_Class_info型常量项
根据CONSTANT_Class_info项常量项的结构可知其index数据项又是一个索引项,指向全限定名常量项索引,index数据项的值为0X12=18,表示指向第18 个常量项,根据constant_pool_count的值为19可得,常量池中一共有18个常量项,巧了正好在最后一个,但是要知道18个常量项必须知道前17个常量项所占字节,这里就不一一找了,最后找到第18个常量项CONSTANT_Utf8_info在 class文件中包含的内容如下:
第18个常量项
根据tag等于1得第18项是CONSTANT_Utf8_info型,该类型存储 UTF-8编码的字符串,在TestClass.class文件种该常量项种个数据项的内容如下: length(u2):表示UTF-8编码的字符串占用的字节数,
值为0x0010=16.
bytes(u1):表示长度为length的UTF-8编码的字符串. 因为length=16,所以 length后面紧跟的长度为16个
字节的连续数据是一个使用UTF-8缩略编码表示的字符串。后面紧跟的第一个字节为0x6A=106,那该编码代表的字符为j,我们发现106其实就是字符j对应的ASCII码。后面16个字节代表的字符就是: java/lang/Object
到此为止,我们得到了第一个常量项CONSTANT_Methodref_info的第二个数据项index指向的内容为CONSTANT_Class_info常量项, CONSTANT_Class_info常量的第二个数据项index指向CONSTANT_Utf8_info 常量项,CONSTANT_Utf8_info常量项的内容为 java/lang/Object 。
当然CONSTANT_Methodref_info常量项还有第三个数据项index,其存放的也是一个其他常量的索引。
根据上面的找法我们就可以找出常量池中包含的内容:字面量和符号引用。
采用javap命令分析class文件
根据上面的找法我们就可以找出常量池中包含的内容:字面量和符号引用。java考虑到这种找法太麻烦了,所以提供了一个命令javap来帮助我们分析class文件的内容。
javap分析class文件用法:javap -verbose class文件名
作者:zlcook
链接:https://www.jianshu.com/p/d8492e748c57
來源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。上面通过javap命令得到的结果,该结果显示的很友好,由过上面的
第一个常量项 | 第几个 | tag | index | index | 最终代表的内容 |
class中16 进制值 | 0X0A | 0X004 | 0X000F | ||
转换成10 进制值 | 10 | 4 | 15 | 查完4和15才知道 | |
javap分析 | #1 | Methodre | #4 | #15 | java/lang/Object."<init>":()V |
理论我们可以很清楚的看到常量池一共18项:其中第一项如下:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
和我们通过手动方式查看第一个常量项CONSTANT_Methodref_info 对比一下就知道javap显示的内容是多么友好了。
class文件中包含的内容
下面我们来看一下class文件中常量池的内容和java源码中的内容。
TestClass.java代码内容
package com.zlcook.clazz? public class TestClass{ private int m? public int inc(){ return m+1? } } |
TestClass.class中常量池内容:
再复习一下常量池中主要存放字面量:如文本字符串、声明为final的常量值等。和符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
所以出现com/zlcook/clazz/TestClass、java/lang/Object、m、inc 都是应该的,那么I、V、、LineNumberTable都是什么?那肯定是字段描述符或者是方法描述符了。这部分是编译时自动生成的,它们会被class文件中其它部分(字段表field_info、方法表method_info、属性表
attribute_info)引用到,它们会用来描述一些不方便使用"固定字节"进行表达的内容。譬如描述方法的返回值是什么?有几个参数?每个参数的类型是什么?因为Java中的"类"是无穷无尽的,无法通过简单的无符号字节来描述一个方法用到了什么类,因此在描述方法的这些信息时,需要引用常量表中的符号引用进行表达。
哪些字面量会进入常量池中
我们知道class文件存放字面量:如文本字符串、声明为final的常量值等。这里的"等"就挺烦人。
下面我们来看看哪些字面量会进入常量池。(jdk1.8.0环境)
8种基本类型:
上面代码测试结果:
final类型的8种基本类型的值会进入常量池。
非final类型的8种基本类型的值double、float、long的值会进入常量池,包括long_delay_num的值。
StringTest.class的常量池种包含内容:
常量池中包含的字符串类型字面量
以上是关于Jvm(35),class文件结构----常量池的主要内容,如果未能解决你的问题,请参考以下文章
JVM14_Class文件结构细节魔数Class文件版本常量池访问标识(或标志)类索引|父类索引|接口索引集合字段|方法|属性表集合