Java中几种常量池比较

目录

class常量池

什么是字面量和符号引用

字符串常量池

字符串常量池的设计思想

字符串常量池的位置

字符串常量池内部结构

字符串常量池里放的是什么?

运行时常量池

总结


Java中主要有三种常量池,分别是class常量池、字符串常量池和运行时常量池。我们对这三个常量池进行对比。

class常量池

我们写的每一个Java类被编译之后都会生成一个对应的Class文件。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。每个类都有一个Class常量池。

什么是字面量和符号引用

字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值和八种基本类型的变量等。而符号引用则属于编译原理方面的概念,包括了下面三类变量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符 

Java代码在进行Javac编译的时候是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或者运行时解析、翻译到具体的内存地址中。

常量池中每一项常量都是一个表,在JDK1.7之前共有11种结构各不相同的表结构数据,在JDK1.7中为了更好地支持动态语言调用,又额外增加了3种。

字符串常量池

字符串常量池的设计思想

和其他对象分配一样,字符串的分配耗费高昂的时间和空间代价,作为最基础的数据类型大量频繁创建字符串会极大的影响性能。

JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化

  • 为字符串开辟一个字符串常量池,类似于缓存区

  • 创建字符串常量时,首先检查字符串常量池是否存在该字符串

  • 若存在该字符串,返回引用实例;若不存在,实例化该字符串并放入池中

实现的基础

  • 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享

  • 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收

字符串常量池的位置

在JDK1.6之前,字符串常量池在方法区,在JDK1.7,字符串常量池被移入了堆中。移入堆中的原因大概是方法区的内存空间太小了。

字符串常量池内部结构

  • 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
  • 在JDK1.6中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
  • 在JDK1.7中,StringTable的长度可以通过参数指定:
-XX:StringTableSize=66666

字符串常量池里放的是什么?

  • 在JDK1.6及之前版本中,String Pool里放的都是字符串常量;
  • 在JDK1.7中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。

需要说明的是:字符串常量池中的字符串只存在一份! 
如:

String s1 = "hello,world!";
String s2 = "hello,world!";

即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。

运行时常量池

  • 运行时常量池存在于内存中的方法区,class常量池被加载到内存之后会存放在运行时常量池中。除了保存Class文件中描述的符号引用外,还会把符号引用翻译为直接引用,并存储在运行时常量池中。
  • 运行时常量池相对于Class文件常量来说另一个特征是它是动态的。运行时可以将新的常量放入池中,比如(String#intern())
  • JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

总结

  存放位置 静态还是动态 存放的内容
Class常量池 Class文件中 静态 编译期生成的各种字面量和符号引用
字符串常量池 方法区 静态

JDK1.6及之前,存放的都是字符串常量

JDK1.7之后,还可以存放对字符串对象的引用

运行时常量池 方法区 动态 class常量池在类加载之后存放在其中,还有动态放入的新的常量(比如String#intern())

参考资料:

https://blog.csdn.net/zm13007310400/article/details/77534349

https://segmentfault.com/a/1190000009888357

《深入理解Java虚拟机》周志明 著

猜你喜欢

转载自blog.csdn.net/zxm490484080/article/details/81181688