在Java中,常量池的概念想必不少人都据说过。这也是面试中比较常考的题目之一。在Java有关的面试题中,通常习惯经过String的有关问题来考察面试者对于常量池的知识的理解,几道简单的String面试题难倒了无数的开发者。因此说,常量池是Java体系中一个很是重要的概念。java
谈到常量池,在Java体系中,共用三种常量池。分别是字符串常量池、Class常量池和运行时常量池。程序员
本文是《好好说说Java中的常量池》系列的第一篇,先来介绍一下到底什么是Class常量池。面试
在Java代码的编译与反编译那些事儿中咱们介绍过Java的编译和反编译的概念。咱们知道,计算机只认识0和1,因此程序员写的代码都须要通过编译成0和1构成的二进制格式才可以让计算机运行。编程
咱们在《深刻分析Java的编译原理》中提到过,为了让Java语言具备良好的跨平台能力,Java独具匠心的提供了一种能够在全部平台上都能使用的一种中间代码——字节码(ByteCode)。vim
有了字节码,不管是哪一种平台(如Windows、Linux等),只要安装了虚拟机,均可以直接运行字节码。数组
一样,有了字节码,也解除了Java虚拟机和Java语言之间的耦合。这话可能不少人不理解,Java虚拟机不就是运行Java语言的么?这种解耦指的是什么?编程语言
其实,目前Java虚拟机已经能够支持不少除Java语言之外的语言了,如Groovy、JRuby、Jython、Scala等。之因此能够支持,就是由于这些语言也能够被编译成字节码。而虚拟机并不关心字节码是有哪一种语言编译而来的。工具
Java语言中负责编译出字节码的编译器是一个命令是javac
。布局
javac是收录于JDK中的Java语言编译器。该工具能够将后缀名为.java的源文件编译为后缀名为.class的能够运行于Java虚拟机的字节码。学习
如,咱们有如下简单的HelloWorld.java
代码:
public class HelloWorld {
public static void main(String[] args) {
String s = "Hollis";
}
}
复制代码
经过javac命令生成class文件:
javac HelloWorld.java
复制代码
生成HelloWorld.class
文件:
如何使用16进制打开class文件:使用
vim test.class
,而后在交互模式下,输入:%!xxd
便可。
能够看到,上面的文件就是Class文件,Class文件中包含了Java虚拟机指令集和符号表以及若干其余辅助信息。
要想可以读懂上面的字节码,须要了解Class类文件的结构,因为这不是本文的重点,这里就不展开说明了。
读者能够看到,
HelloWorld.class
文件中的前八个字母是cafe babe
,这就是Class文件的魔数(Java中的”魔数”)
咱们须要知道的是,在Class文件的4个字节的魔数后面的分别是4个字节的Class文件的版本号(第五、6个字节是次版本号,第七、8个字节是主版本号,我生成的Class文件的版本号是52,这时Java 8对应的版本。也就是说,这个版本的字节码,在JDK 1.8如下的版本中没法运行)在版本号后面的,就是Class常量池入口了。
Class常量池能够理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各类字面量(Literal)和符号引用(Symbolic References)。
因为不一样的Class文件中包含的常量的个数是不固定的,因此在Class文件的常量池入口处会设置两个字节的常量池容量计数器,记录了常量池中常量的个数。
固然,还有一种比较简单的查看Class文件中常量池的方法,那就是经过javap
命令。对于以上的HelloWorld.class
,能够经过
javap -v HelloWorld.class
复制代码
查看常量池内容以下:
从上图中能够看到,反编译后的class文件常量池中共有16个常量。而Class文件中常量计数器的数值是0011,将该16进制数字转换成10进制的结果是17。
缘由是与Java的语言习惯不一样,常量池计数器是从0开始而不是从1开始的,常量池的个数是10进制的17,这就表明了其中有16个常量,索引值范围为1-16。
介绍完了什么是Class常量池以及如何查看常量池,那么接下来咱们就要深刻分析一下,Class常量池中都有哪些内容。
常量池中主要存放两大类常量:字面量(literal)和符号引用(symbolic references)。
前面说过,运行时常量池中主要保存的是字面量和符号引用,那么到底什么字面量?
在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎全部计算机编程语言都具备对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有不少也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。
以上是关于计算机科学中关于字面量的解释,并非很容易理解。说简单点,字面量就是指由字母、数字等构成的字符串或者数值。
字面量只能够右值出现,所谓右值是指等号右边的值,如:int a=123这里的a为左值,123为右值。在这个例子中123就是字面量。
int a = 123;
String s = "hollis";
复制代码
上面的代码事例中,123和hollis都是字面量。
本文开头的HelloWorld代码中,Hollis就是一个字面量。
常量池中,除了字面量之外,还有符号引用,那么到底什么是符号引用呢。
符号引用是编译原理中的概念,是相对于直接引用来讲的。主要包括了如下三类常量: * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符
这也就能够印证前面的常量池中还包含一些com/hollis/HelloWorld
、main
、([Ljava/lang/String;)V
等常量的缘由了。
前面介绍了这么多,关于Class常量池是什么,怎么查看Class常量池以及Class常量池中保存了哪些东西。有一个关键的问题没有讲,那就是Class常量池到底有什么用。
首先,能够明确的是,Class常量池是Class文件中的资源仓库,其中保存了各类常量。而这些常量都是开发者定义出来,须要在程序的运行期使用的。
在《深刻理解Java虚拟》中有这样的表述:
Java代码在进行Javac
编译的时候,并不像C和C++那样有“链接”这一步骤,而是在虚拟机加载Class文件的时候进行动态链接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,所以这些字段、方法的符号引用不通过运行期转换的话没法获得真正的内存入口地址,也就没法直接被虚拟机使用。当虚拟机运行时,须要从常量池得到对应的符号引用,再在类建立时或运行时解析、翻译到具体的内存地址之中。关于类的建立和动态链接的内容,在虚拟机类加载过程时再进行详细讲解。
前面这段话,看起来很绕,不是很容易理解。其实他的意思就是: Class是用来保存常量的一个媒介场所,而且是一个中间场所。在JVM真的运行时,须要把常量池中的常量加载到内存中。
至于到底哪一个阶段会作这件事情,以及Class常量池中的常量会以何种方式被加载到具体什么地方,会在本系列文章的后续内容中继续阐述。欢迎关注个人博客(www.hollischuang.com) 和公众号(Hollis),便可第一时间得到最新内容。
另外,关于常量池中常量的存储形式,以及数据类型的表示方法本文中并未涉及,并非说这部分知识点不重要,只是Class字节码的分析本就枯燥,做者不想在一篇文章中给读者灌输太多的理论上的内容。感兴趣的读者能够自行Google学习,若是真的有必要,我也能够单独写一篇文章再深刻介绍。
《深刻理解java虚拟机》 《Java虚拟机原理图解》 1.2.二、Class文件中的常量池详解(上)