当前位置: 首页 > 科技观察

再来说说Java中的常量池,Class常量池

时间:2023-03-13 16:18:37 科技观察

在Java中,想必很多人都听说过常量池的概念。这是面试中被问得最多的问题之一。在Java相关的面试题中,一般习惯用String相关的题来考察面试者对常量池知识的理解程度。几个简单的String面试题难倒了无数开发者。因此,常量池是Java系统中一个非常重要的概念。说到常量池,在Java系统中,常量池分为三种。它们是字符串常量池、类常量池和运行时常量池。本文是《好好说说Java中的常量池》系列的第一篇。先介绍一下什么是Class常量池。什么是类文件?在Java代码的编译和反编译中,我们引入了Java编译和反编译的概念。我们知道计算机只知道0和1,所以程序员写的代码需要编译成0和1组成的二进制格式,计算机才能运行。正如我们在深入分析Java的编译原理中提到的,为了让Java语言具有良好的跨平台能力,Java独树一帜地提供了一种可以在所有平台上使用的中间代码——字节码(ByteCode)。有了字节码,不管在什么样的平台(如Windows、Linux等),只要安装好虚拟机,就可以直接运行字节码。同样,有了字节码,Java虚拟机和Java语言之间的耦合度也被解除了。很多人可能不理解这一点。Java虚拟机不就是运行Java语言吗?这个脱钩指的是什么?其实Java虚拟机已经可以支持很多Java语言以外的语言了,比如Groovy和JRuby、Jython、Scala等,之所以能支持是因为这些语言也可以编译成字节码。虚拟机不关心字节码是用哪种语言编译的。Java语言中负责编译字节码的编译器是一个叫做javac的命令。javac是JDK中包含的Java语言编译器。该工具可以将后缀为.java的源文件编译为后缀为.class的字节码,可以在Java虚拟机上运行。例如,我们有如下简单的HelloWorld.java代码:publicclassHelloWorld{publicstaticvoidmain(String[]args){Strings="Hollis";}}通过javac命令生成class文件:如何使用16-in打开class文件:使用vimtest.class,然后在交互模式下,输入:%!xxd。可以看出,上面的文件就是Class文件,Class文件中包含了Java虚拟机指令集和符号表以及其他一些辅助信息。要想能够看懂上面的字节码,就需要了解Class类文件的结构。由于这不是本文的重点,这里不再赘述。读者可以看到HelloWorld.class文件的前八个字母是cafebabe,这就是Class文件的幻数(Java中的“幻数”)。我们需要知道的是,在Class文件的4个字节中,magicnumber之后就是这个4字节Class文件的版本号(第5、6字节是次版本号,第7、8字节是主版本号)版本号,我生成的Class文件的版本号是52,此时对应的是Java8的版本。也就是说这个版本的字节码不能在JDK1.8以下的版本中运行)在版本号之后,它是Class常量池的入口。Class常量池Class常量池可以理解为Class文件中的资源仓库。Class文件中除了包含类版本、字段、方法、接口等描述信息外,还有一条信息就是常量池表,用来存放各种字面量(Literal)和符号引用(符号引用)。由于不同Class文件包含的常量个数不固定,所以在Class文件的常量池入口处设置一个2字节的常量池容量计数器,用于记录常量池中常量的个数。当然,还有一种比较简单的方法可以查看Class文件中的常量池,那就是通过javap命令。对于上面的HelloWorld.class,可以通过javap-vHelloWorld.class查看常量池的内容,如下:?从上图可以看出,反编译的class文件常量池中有16个常量。Class文件中常量计数器的值为0011,十六进制转十进制结果为17,原因是:与Java的语言习惯不同,常量池计数器从1开始,而不是从0开始,而数ofconstantpools的十进制是17,表示里面有16个常量,索引取值范围是1-16。我们将深入分析Class常量池中的内容。常量池中存储的常量主要有两种类型:字面量和符号引用。Literals前面提到,运行时常量池主要存放的是字面量和符号引用,那么到底什么是字面量呢?在计算机科学中,字面量是一种用于表示源代码中固定值的符号(notation)。几乎所有的计算机编程语言都有基本值的文字表示,例如整数、浮点数和字符串;许多还支持布尔和字符类型的文字表示;甚至有的支持枚举类型的元素,像数组、记录、对象这样的复合类型的值也支持字面量表示法。以上是计算机科学中对字面量的解释,不太好理解。简单来说,字面量是指由字母、数字等组成的字符串或值,字面值只能以右值的形式出现。所谓右值是指等号右边的值,如:inta=123其中a为左值,123为右值。在此示例中,123是文字值。inta=123;Strings="霍利斯";在上面的代码示例中,123和hollis都是文字。在本文开头的HelloWorld代码中,Hollis是一个字面量。在符号引用常量池中,除了字面量,还有符号引用,那么什么是符号引用呢?符号引用是编译原理中的一个概念,是相对于直接引用而言的。主要包括以下三类常量:类和接口的完全限定名字段名和描述符方法名和描述符这也证明了前面的常量池也包含了一些com/hollis/HelloWorld,main,([Ljava/lang/String;)V等常量的原因。Class常量池有什么用?什么是Class常量池,如何查看Class常量池,Class常量池中存储了什么,我已经介绍了这么多。还有一个关键问题没有讨论,就是Class常量池有什么用。首先要明确的是,Class常量池是Class文件中的一个资源仓库,里面存放着各种常量。这些常量由开发人员定义,需要在程序运行期间使用。在《深入理解Java虚拟》中,有这样的说法:Java代码用Javac编译时,没有像C、C++那样的“连接”步骤,而是在虚拟机加载Class文件时动态连接。也就是说,每个方法和字段最终的内存布局信息都不会保存在Class文件中,所以如果这些字段和方法的符号引用不经过运行时转换,就无法获取到真正的内存入口地址,而虚拟机不能直接访问它。使用。虚拟机在运行时,需要从常量池中获取相应的符号引用,然后在类创建或运行时解析翻译成具体的内存地址。关于类创建和动态连接的内容,在虚拟机类加载过程中会详细讲解。这段话看起来很绕,不容易理解。其实他的意思是:Class是一个中间的地方,用来保存常量,是一个中间的地方。JVM真正运行时,需要将常量池中的常量加载到内存中。至于具体在哪个阶段进行,Class常量池中的常量如何加载到具体的地方,会在本系列文章的后续内容中进行说明。欢迎关注我的博客(https://www.hollishuang.com)和?(Hollis),第一时间获取最佳内容。另外,常量池中常量的存储形式和数据类型的表示方法本文没有涉及,不是说这部分知识不重要,而是Class字节码的分析比较无聊,作者不想在一篇文章中给读者灌输过多的理论内容。有兴趣的读者可以自行谷歌学习。如果真的有必要,我也可以单独写一篇文章,深入介绍一下。【本文为专栏作家霍利斯原创文章,微信公众号Hollis(ID:hollishuang)撰文】点此阅读更多本作者好文