`

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

    1. 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建 2. JVM为了提高性能和减少内存开销,在实例化字符串

    第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节: ...

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

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

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

    (1)类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太 (2)永久代会为 GC 带来不必要的复杂度,并且回收效率偏

    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常量池

    主要介绍了深入探索Java常量池,涉及静态常量池和运行时常量池的介绍,常量池的好处,8种基本数据类型的包装类和常量池等相关内容,具有一定参考价值,需要的朋友可以了解下。

    Java常量池理解与总结

     在Class文件结构中,头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于...

    图解JVM的内存结构及字符串常量池方法详解.docx

    JVM包含了非常多的知识,比较核心的有 内存结构 、 类加载 、 类文件结构 、 垃圾回收 、 执行 引擎 、 性能调优 、 监控 等等这些知识,但所有的功能都是围绕着 内存结构 展开的,因为我们编译后的...

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

    1. 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建 2. JVM为了提高性能和减少内存开销,在实例化字符串

    jvm如何处理长字符串

    jvm如何处理长字符串?java的classs文件中,constant_utf8_info的长度是u2,也就是说,一个字符串最长是65535个字节,但是,在本机做测试,超过这个长度的字符串也是允许的,原因是什么?

    字符数组的存储方式 字符串常量池.docx

    字符串在java程序中被大量使用,为了避免每次都创建相同的字符串对象及内存分配,JVM内部对字符串对象的创建做了一定的优化,在Permanent Generation中专门有一块区域用来存储字符串常量池(一组指针指向Heap中的...

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

    Java作为一种平台无关性的语言,其主要依靠于Java虚拟机——JVM,接下来通过本文给大家介绍JVM之内存结构及字符串常量池的相关知识,需要的朋友可以参考下

    JVM内存模型架构图-新生代-老年代-永久代

    JVM内存模型架构图,核心部分包括: GC主要在新生区(伊甸园区)、老年区 新生区(伊甸园区(对象都是在这个区new出来的)、幸存区to、幸存区from:幸存区位置会互相交换... jdk1.8之后:无永久代,常量池在元空间中

    JVM执行子系统-JVM进阶

    常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项 u2 类型的数据,代 表常量池容量计数值(constant_pool_count)。与 Java 中语言习惯不一样的是,这个容 量计数是从 1 而不是 0 开始的 常量池中主要...

    Java中的字符串常量池详细介绍

    主要介绍了Java中的字符串常量池详细介绍,JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池,需要的朋友可以参考下

    jvm原理及调优

    一、JVM概述 二、JVM的体系结构 三、JVM运行时数据区 3.1 PC寄存器 3.2 JVM栈 3.3 堆(Heap) 3.4 方法区域 3.5 运行时常量池 3.6本地方法堆栈 四、Jvm堆 五、Jvm调优

    JVM虚拟机从入门到实战视频教程.zip

    目录网盘文件永久链接 001-JVM课程导读 002-第一章-JVM课程简介 003-虚拟机概念 004-JVM的定义 005-JVM规范 006-JVM产品 ...025-【分析】常量池总数 026-【分析】class文件中的常量 027..............

    Java进阶教程解密JVM视频教程

    学习字节码指令的的运行流程,字节码指令与常量池、方法区的关系。掌握条件分支、循环控制、异常处理、构造方法在字节码级别的实现原理,利用HSDB工具理解多态原理。还会涉及从编译期的语法糖处理,到类加载的各个...

    JVM教程吐血整理干货.md

    JVM JVM运行时内存分区 程序计数器 程序计数器的特点 Java虚拟机栈 栈帧 局部变量表 操作数栈 ...(永久代和元空间都是方法区的实现),字符串常量池也移动到了heap空间 jdk8之后的jvm内存分区 程

Global site tag (gtag.js) - Google Analytics