JVM 深入理解 - 字符串常量池

本文深入探讨JVM中的字符串常量池概念,解析其作用、特性、存放位置及数据结构,通过代码示例说明如何利用字符串常量池优化性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Java 学习目录
上一章 JVM 深入理解 - 内存区域

说明

JVM 中的字符串常量池是一个非常有争议的概念,各类书籍和网站上众说纷纭。而且没有官方定义,所以我们从他的作用和 JVM 设计他解决什么问题来分析它。

字符串常量池

字符串驻留

字符串驻留(String interning)是字符串常量池产生的根本原因。英文维基上解释,大意如下:

所谓字符串驻留,是指在系统中,对每个字面量唯一的字符串,都只保留唯一的一份副本,称作“驻留量”(intern),并且它们都是不可变的。这些彼此不同的字符串被存储在字符串常量池中。

各编程语言有各自的方法来取得字符串常量池中的驻留量,或者将一个字符串驻留,比如Java中的String.intern()。在Java中,所有编译期能确定的字符串也都会自动驻留。

字符串字面量

前面提到了字符串字面量(String literal)的概念。Java语言规范中说:

字符串字面量是双引号括起来的0或多个字符。它是对String类实例的引用。
一个字符串字面量总是引用String类的同一个实例。这是因为字符串字面量以及字符串常量表达式都通过使用String.intern()方法而驻留了,从而可以共享唯一的实例。

什么是字符串常量池

在 JVM 运行期间字符串所占用的空间绝对是数一数二的,而字符串常量池就是针对字符串在 JVM 所占空间进行的优化手段。

  • 首先对象分配需要付出时间和空间上的开销,字符串可以说是和 8 个基本类型一样常用的类型,甚至比 8 个基本类型更加常用,频繁的创建字符串对象,对性能的影响是非常大的,所以用常量池的方式可以很大程度上降低对象创建、分配内存空间的开销,从而优化 JVM 的性能。
  • 当然 JVM 在 8 种基本类型上也是有优化的。8 种基本类型的常量池都是系统协调的。
    比如 Integer [-128,127] 区间内的 Integer 被缓存在内部类 IntegerCache 中,这个类就相当于整形常量池。在该区间内两个数值相同的整形值,在自动装箱后实际上指向堆内的同一个 Integer 对象(也就是驻留量),可以参考Integer.valueOf()方法的源码,当然这个大小是可以设置的。
    -XX:AutoBoxCacheMax= size
    后边会有一个章节专门讲解。

特性

常量池中不存在两个相同的对象

存放位置

JDK 1.6 方法区(永久代)。
JDK 1.7 以上(含)实际存储地址是 “堆”(但逻辑上还是归 方法区 的,因为 JVM 规范是这么规定的)。

数据结构

在 HotSpot 中字符串常量池是通过一个 StringTable 类(一个 Hash 表,非 Java 实现)实现的;这个 StringTable 在每个 HotSpot 的实例中只有一份,被所有的线程共享。

存放内容

在 JDK 1.6 及之前版本:字符串常量池中放的都是字符串常量;
在 JDK 1.7 中:由于 String.intern() 发生了改变,因此字符串常量池中存放放于堆内的字符串对象的引用。

放入规则

什么样的数据会放入字符串常量池中?

  • 直接使用双引号声明出来的 String 对象会直接存储在常量池中。也就是说,编译器可以识别的字符串字面量都可以放入字符串常量池中。
  • 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中。

深入了解

我们都简单的了解了字符串常量池的特性,以及简单的原理了。下面我们通过代码来了解下。

  • String 部分源码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
   
    .........
   
     /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();
}

简单看了一下上边的方法与属性,下面我们来说一下四个点。

  • public final class String implements java.io.Serializable, Comparable< String >, CharSequence
  1. String 类是 使用了 final 就说明这个类是不可继承。也就保证了 String 的安全性。
  2. Serializable 可序列化。
  3. Comparable 用于字符串的比较
  • private final char value[];
  1. 上边代码所描述的是一个不可实例化的数组,但声明方式为什么是这样,不应该是声明为“ private final char[] value;”吗?这也是数组的一种声明方式,只是我们比较少见到,也许是因为历史原因 String 保留使用着这种不常用的数组声明方式。
  • private int hash;
  1. 字符串常量池查重使用的。
  2. 配合其他需要用到 hash 值的地方,例如 HashMap 中的 key - value 使用。
  • public native String intern();
    如果仔细看一下上面的 Java Doc 他的解释如下。

String类会维护一个私有的、初始为空的字符串池。
当调用 intern() 方法时,如果该池中已经存在与本字符串 this 字面量相同的一个字符串(用 equals() 方法判定),那么就直接返回池中的那个字符串。如果不存在,那么 this 会被加入池中(驻留),并返回对它的引用。
对两个字符串 s 和 t ,当且仅当 s.equals(t) 为真时,s.intern() == t.intern() 才为真。
所有字符串字面量和字符串常量表达式都会被驻留。

代码详解

代码说完了下面我们看图说话。
在这里插入图片描述
开局一张图剩下全靠编,大家好下面来到我编你听阶段。

public class StringTest {

	public static void main(String[] args) {
		String a1 = "吴勉";
		String a2 = "吴勉";
		String b1 = new String("吴勉");
		String b2 = b1.intern();
		System.out.println("编号 :1 ; " + a1 == a2);
		System.out.println("编号 :2 ; " + a2 == b1);
		System.out.println("编号 :3 ; " + a2 == b2);
	}
}

结果 :true,false,true ;
为什么会这样呢,我们配合一张图来说明
在这里插入图片描述

  • 编号 1
    这个值是 true 的原因是因为在编译器就发现的 “吴勉” 这个值所以将这个值放入了字符串你常量池。
    经过是:
  1. a1 定义字面量 吴勉
  2. 根据字面量 吴勉 的 hash 值查找字符串常量池中的管理表,查找是否有该 hash 值。
  3. 没有,将吴勉作为字符串驻留,存入字符串常量池中。并返回该引用。
  4. a2 定义字面量 吴勉
  5. 根据字面量 吴勉 的 hash 值查找字符串常量池中的管理表,查找是否有该 hash 值。
  6. 有,返回该引用。
  • 编号 2
    String b1 = new String(“吴勉”);
    b1 在创建时新建的一个 String 对象,然后 String 对象在引用字符串常量池中的 “吴勉”
    所以 a2 = b1 的比较是那 吴勉在字符串常量池中的引用与 new String 这个对象的内存地址进行比较所以不等。
  • 编号 3
    String b2 = b1.intern();
    这个 intern 方法前边也说了。
    所以他获取的是字符串常量池中的引用所以。他一定也是同一个引用。

好就说到这里吧! 还有很多超级多的细节,以后可能会讲解,现在没法详细说,你们如果有时间可以尝试使用。
JPS 加 java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB 测试一下相关内容
在这里插入图片描述
JHSDB 工具可以看见的内容有很多,这里简单介绍下一下内容。

  1. 当前启动项目的所有线程
  2. 加载的类
  3. 堆内的所有对象
  4. 栈信息
  5. 对内存分配

总结:

  • 做好 String 字符串性能优化,可以提高系统的整体性能。在这个理论基础上,Java 版本在迭代中通过不断地更改成员变量,节约内存空间,对 String 对象进行优化。
  • 千里之堤,溃于蚁穴。日常编程中,我们往往可能就是对一个小小的字符串了解不够深入,使用不够恰当,从而引发线上事故。

Java 学习目录
上一章 JVM 深入理解 - 内存溢出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值