`

jvm的常量池

阅读更多
在class文件中,“常量池”是最复杂也最值得关注的内容。

Java是一种动态连接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值还,还包含一些以文本形式出现的符号引用,比如:

类和接口的全限定名;

字段的名称和描述符;

方法和名称和描述符。

在C语言中,如果一个程序要调用其它库中的函数,在连接时,该函数在库中的位置(即相对于库文件开头的偏移量)会被写在程序中,在运行时,直接去这个地址调用函数;

而在Java语言中不是这样,一切都是动态的。编译时,如果发现对其它类方法的调用或者对其它类字段的引用的话,记录进class文件中的,只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。

所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很非富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。

常量池由多条“常量池项”组成,每一个常量池项又由两部分组成,这里分别称为“常量池项头”和“常量池项体”。

常量池项头表明常量池项的类型,常量池项共分为11种类型,分别为:

常量池项类型



说明

CONSTANT_Utf8

1

UTF-8编码的Unicode字符串

CONSTANT_Integer

3

int型常量

CONSTANT_Float

4

Float型常量

CONSTANT_Long

5

Long型常量

CONSTANT_Double

6

double型常量

CONSTANT_Class

7

对一个class的符号引用

CONSTANT_String

8

String型常量

CONSTANT_Fieldref

9

对一个字段的符号引用

CONSTANT_Methodref

10

对一个类方法的符号引用

CONSTANT_InterfaceMedthodref

11

对一个接口方法的符号引用

CONSTANT_NameAndType

12

对名称和类型的符号引用

常量池项体中存放的就是对应的常量数据,比如各种数值型的常量或者字符串等等。

以下介绍kvm中的常量池是如何组织起来的。



数据结构:

在KVM的头文件kvm/vmcommon/h/pool.h中,有以下对常量池项类型的定义:

Word-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">#define CONSTANT_Utf8                       1
#define CONSTANT_Integer                    3
#define CONSTANT_Float                      4
#define CONSTANT_Long                       5
#define CONSTANT_Double                     6
#define CONSTANT_Class                      7
#define CONSTANT_String                     8
#define CONSTANT_Fieldref                   9
#define CONSTANT_Methodref                  10
#define CONSTANT_InterfaceMethodref    11
#define CONSTANT_NameAndType            12 以及常量池项体结构的定义: union constantPoolEntryStrUCt {
    struct {
        unsigned short classIndex;
        unsigned short nameTypeIndex;
    }               method;  /* Also used by Fields */
    CLASS           clazz;
    INTERNED_STRING_INSTANCE String;
    cell           *cache;   /* Either clazz or String */
    cell            integer;
    long            length;
    NameTypeKey     nameTypeKey;
    NameKey         nameKey;
    UString         ustring;
};
class文件中,常量池项有很多种类,每一个常量池项的大小都不同,而对于常量池的使用又是如此之多,最好能够使用数组来索引,这样可以提高效率,所以KVM里使用union来代表一个常池项,union的每一项是常量池项的一种可能的数据类型,这样每一项都有了相同的大小,可以构造数组。

显然,这个数组就将是常量池的核心内容,那么这个数组放在哪里呢?就在下面这个结构中:

struct constantPoolStruct {
    union constantPoolEntryStruct entries[1];
};



这就是常量池。这个常量池的设计很有意思:

1、这个结构体中只有一个指针,指向一个常量池项体数组,数组中元素的个数是常量池项数+1,数组中的第一项(即序号为0的那一项)不是实际的常量池项体,而是存放了常量池项的数目,即表明了数组中接下来的元素数。要取得数组的长度信息,只有一个办法,就是读数组的第一个元素,为不造成空指针错误,所以constantPoolStruct在定义的时候就要保证数组的第0个元素必须存在,所以上面的entries在定义时就被指定为长度为1的数组。

单纯从数据结构的设计角度来看,我认为constantPoolStruct的设计并不是很清晰,使用数组的第一个无素来表示数组的长度多少一点显得混乱,明明可以在constantPoolStruct的结构里增加一个变量来表明数组长度,这样不是更清晰吗?之所以这样做,我想也是与class文件中常量池的设计惯例有关。在class文件中, constant_pool紧跟在constant_pool_count之后,而constant_pool_count = constant_pool中实际的项数+1,相当于constant_pool_count也把自己当成了常量池中的第一项。

由此可见,KVM的常量池设计与class文件如出一辙。

2、常量池项体以一个union来表示,而union不带有自身类型的信息,如何知道一个常量池项的类型呢?

在一个class文件的常量池被载入后,生成了constantPoolStruct结构体的实例,在其中constantPoolEntryStruct数组的最后一项之后,一定会跟随一个字节数组,这个数组中的每一个字节就是一个“常量池项头”,长度与实际的常量池项数相同,即constant_pool_count-1,在这个字节中就指明了相应常量池项的类型。



程序实现:

构造常量池的代码段主要在kvm/vmcommon/src/loader.c的loadConstantPool()函数中,函数原形如下:

static POINTERLIST

loadConstantPool(FILEPOINTER_HANDLE ClassFileH, INSTANCE_CLASS CurrentClass);

两个参数分别为类文件的句柄以及当前被载入类的指针。

这个函数的总体流程如下:

1- 循环读取文件中常量池中所有项,把,把各项内容存入临时数组RowPool中;(L649~L740)

2- 计算常量池所占空间大小(以constantPoolEntryStruct枚举体数计),并申请常量池空间;(L742~L757)

3- 循环读取暂存在RowPool中的常量信息,为常量池赋值。

其中第2步值得一看,记算空间大小的那一行如下:





int tableSize = numberOfEntries + ((numberOfEntries + (4 - 1)) >> 2);
一个constantPoolEntryStruct枚举体的大小为4,前面讲过,在constantPoolEntryStruct数组的后要跟有一个字节数组来存放常量池项的类型信息,即每一个constantPoolEntryStruct要对应1个字节的常量池项头,所以当以constantPoolEntryStruct枚举体数为单位给常量池项头数组申请空间时,需要向4字节对齐,每多1~4个常量池项头,就要多申请一个constantPoolEntryStruct。这一句就是这个意思。

loadConstantPool函数执行过程中,会把新生成的常量池指针赋给CurrentClass->constPool,这样,这个类实例中就有完整的常量池了。

进入讨论组讨论。
分享到:
评论

相关推荐

    JVM常量池教程吐血整理干货.md

    JVM常量池 Class常量池(静态常量池) 运行时常量池 字符串常量池(全局常量池) 包装类型缓存池 JVM常量池 Jvm常量池分为: Class常量池(静态常量池) 运行时常量池 字符串常量池(全局常量池) 包装类型缓存池 Class常量...

    06-VIP-JVM调优实战及常量池详解(1)1

    当一个新字符串被创建时,JVM会检查常量池中是否存在相同的字符串,如果存在,就直接返回其引用,否则会在堆中创建一个新的实例并添加到常量池。 垃圾收集(Garbage Collection, GC)是JVM管理内存的重要机制。GC...

    第4节: 揭秘JVM字符串常量池和Java堆-01

    第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: ...

    JDK8的JVM内存结构,元空间替代永久代成为方法区及常量池的变化1

    常量池分为静态常量池、运行时常量池、字符串常量池和整型常量池。静态常量池存在于每个*.class文件中,包含了字面量和符号引用,这部分在类加载的链接阶段会被解析成直接引用。运行时常量池则是在虚拟机运行时载入...

    06-VIP-JVM调优实战及常量池详解(预习)1

    《JVM调优实战与常量池详解》 在Java开发中,JVM(Java虚拟机)的性能优化是一项至关重要的任务。通过对JVM进行调优,我们可以显著提升应用程序的运行效率,减少内存消耗,避免不必要的垃圾回收(GC)带来的性能...

    JVM 50道面试题和答案.docx

    2. **JVM常量池**: - JDK 1.8之前,字符串常量池位于方法区(也称为永久代)。1.8之后,字符串常量池被移到堆中,包括intern()方法后的结果和双引号直接创建的字符串。 - 运行时常量池(Runtime Constant Pool)...

    JVM大厂必备面试题八股文

    JVM常量池中存储的是对象,尤其是JDK 1.8后,String常量池位于堆中。 Java中的对象不一定都在堆上分配,逃逸分析技术可以使某些对象在栈上分配,从而优化内存使用。内存溢出(OOM)和内存泄漏是两种不同的问题,...

    RodJohn#jvm#内存区域_运行时常量池1

    常量池静态常量池即*.class文件中的常量池,用于存放字面量和符号引用运行时常量池是jvm运行期间,存储常量的数据结构运行时常量池概念运行时常量池(Runti

    java内存分配之常量池,栈,堆1

    在Java中,内存主要分为四个区域:寄存器、栈、堆和方法区(包括常量池)。以下是这四个区域的详细说明: 1. **寄存器**: 这是计算机硬件的一部分,用于存储非常快速访问的数据。在Java中,寄存器主要由JVM直接管理...

    java常量池分析.pdf

    Java常量池是Java编程语言中的一个重要概念,它在JVM(Java虚拟机)的运行时数据区中占据着核心地位。常量池是每个类或接口在编译时都会生成的一部分,它存储了各种类型的常量,包括字面量(如字符串、整数、浮点数...

    深入探索Java常量池

    Java常量池是Java虚拟机(JVM)中一个非常重要的概念,它主要分为两种:静态常量池和运行时常量池。静态常量池是class文件中的常量池,包括字符串(数字)字面值、类和方法的信息,占用了class文件的大部分空间。...

    java入门教程:数据类型_运行时常量池.docx

    数据类型决定了变量可以存储的值的种类和大小,而运行时常量池则是Java虚拟机(JVM)内存模型中的一个重要组成部分。 首先,让我们详细讨论Java的数据类型。Java的数据类型分为两大类:基本数据类型和引用数据类型...

    java中常量以及常量池

    1、举例说明 变量 常量 字面量  1 int a=10;  2 float b=1.234f;  3 String c="abc";  4 final long d=10L;  a,b,c为变量,d为常量 两者都是左值;...  运行时常量池:是jvm虚拟机在完成类装

    详解JAVA 常量池

    在JVM中,常量池是Class文件中的一个重要组成部分,主要用于存储类的常量信息。常量池可以分为以下几部分: 1. Class文件常量池:用于存储类的常量信息,例如类的名称、字段、方法等。 2. 运行时常量池:用于存储...

    基于常量池和反编译分析的Java初始化研究.pdf

    本文通过对编译后的字节码文件的常量池和反编译的结果进行分析,从 JVM 的角度对类的初始化和对象的初始化进行更深层次的研究,以期更深刻的理解和掌握 Java 的初始化问题,为程序开发打好基础,提高开发的水平。...

    Java String 字符串常量池解析

    JVM 在实例化字符串常量时,会首先检查字符串常量池中是否存在该字符串,如果存在,则返回引用实例;否则,实例化该字符串并放入池中。 字符串常量池存在于方法区中,是一个共享的存储区域。它维护着一个表,总是为...

    探究Java常量本质及三种常量池(小结)

    Java中的常量池是Java虚拟机(JVM)中的一种机制,用于存储编译期常量和运行期常量。常量池是JVM中的一种重要机制,它可以将常量存储在内存中,并提供快速的访问和共享机制。 Java中的常量池有三种形态:静态常量池...

    深入解析JVM之内存结构及字符串常量池(推荐)

    "深入解析JVM之内存结构及字符串常量池" JVM(Java Virtual Machine)是Java语言的核心组件之一,负责将Java代码编译成机器代码并执行。JVM的内存结构是Java开发者需要了解的基础知识之一,本文将深入解析JVM之内存...

    深入JVM概要 JVM详解

    本文将详细介绍Java虚拟机(JVM)的内部机理和实现原理,从类型的生命周期、方法区、常量池、类加载器、垃圾收集器、栈和局部变量等方面对JVM进行深入解析。 类型的生命周期 类型的生命周期是JVM中最重要的部分,...

    JVM指令手册详细完整版.pdf

    例如ldc命令将int, float或String型常量值从常量池中推送至栈顶,ldc_w命令将int, float或String型常量值从常量池中推送至栈顶(宽索引),ldc2_w命令将long或double型常量值从常量池中推送至栈顶(宽索引)。...

Global site tag (gtag.js) - Google Analytics