字符串常量池与intern方法分析

本文详细解析了Java中String#intern()方法的工作原理及其在不同JDK版本中的行为变化,探讨了字符串常量池的位置变迁及其实现差异,通过案例分析展示了如何正确使用intern()方法以节省内存。

文章重要参考https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html。

1 深入理解String#intern方法以及在各JDK版本中的差异

1.1 字符串常量池

字符串常量池存在的意义是使字符串在运行时速度更快,更节省内存。主要的使用方法有三种。
(1)直接双引号引用的String对象会直接存储在字符串常量池中;
(2)直接new String对象时,如果常量池不存在对应String对象,会在池中新创建;
(3)通过String#intern()方法,从字符串常量池中查询当前字符串是否存在,如果不存在就会将当前字符串放入常量池中。

1.2 字符串常量池在不同JDK版本中的位置

在JDK7之前运行时常量池包含字符串常量池存放在方法区,此时hotspot虚拟机对方法区的实现为永久代。

在JDK7中字符串常量池由方法区单独移到了堆中,运行时常量池剩下的东西还在方法区,仍是永久代。

在JDK8中,hotspot移除了永久代而采用元空间(Metaspace)代替,此时字符串常量池仍在堆中,运行时常量池还在方法区,但方法区的实现由永久代变为元空间。

1.3 String#intern()

定义:返回字符串对象的规范化表示形式。

public native String intern();

需要注意的是,intern()方法是一个本地方法;方法的返回值类型是字符串,内容与该字符串相同,但一定取自字符串常量池。

字符串常量池,初始时为空,由String类所私有维护。当intern()方法触发,如果池中已经包含与该String对象相等的字符串,则返回池中字符串。相等的判断,通过equals(Object)方法来实现。否则,将字符串添加到池中,并返回。可以确定的是,通过字符串对象的intern()方法返回的字符串必然取自字符串常量池。

jdk1.6和jdk1.7最大的区别是jdk1.6中 intern 方法会把首次遇到的字符串实例复制到字符串常量池中,并返回此引用;但在jdk1.7中,只是会把首次遇到的字符串实例的引用添加到常量池中(没有复制),并返回此引用。

1.4 通过案例理解String#intern()方法

代码示例1

String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);

打印结果

jdk6:false false
jdk7:false true

在这里插入图片描述

JDK6中常量池位于PermGen区域中,如图PermGen区和Heap区是独立的区域。由引号声明的字符串直接在字符串常量池中生成,存放在PermGen区,而new生成的String对象存放在Heap区。比较Heap区的对象地址和字符串常量池的对象地址必然是不同的。

Jdk6中,由于PermGen区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m。大量使用intern方法容易导致内存溢出java.lang.OutOfMemoryError: PermGen space。正是因为PermGen区域太小,JDK7对字符串常量池做了迁移,迁移到了Java Heap区域。JDK8中直接取消了PermGen区,以元空间来替代。

在这里插入图片描述

1.4.1 s和s2字符串的分析

String s = new String(“1”); 语句的运行会创建2个对象,一个是“1”字符串存放在常量池,另一个是Java Heap中的String对象。

接着执行s.intern();语句,s对象会去字符串常量池查找是否存在"1"字符串,"1"已存在。

String s2 = “1”; 执行该语句,生成引用s2指向字符串常量池中的"1"对象。s的引用地址在Java Heap区,s的引用地址在常量池,地址明显不同。

1.4.2 s3和s4字符串的分析

String s3 = new String(“1”) + new String(“1”);如果忽略前面的s和s2字符串,执行该语句会产生4个对象,分别是字符串常量池中的"1"对象、Java Heap中s3指向的对象以及两个中间对象。此时s3引用对象的内容是“11”,但是字符串常量池中只有"1",而没有"11"对象。

接着指向s3.intern();语句,字符串常量池没有"11",s3会将"11"放入到String常量池中。如果是JDK6,此时PermGen区的字符串常量池会新生成一个"11"对象。而在JDK7中常量池不在PermGen区域,不需要再存储一个对象,而是直接存储堆中对象引用,这个引用指向s3引用对象。

最后,执行String s4 = “11”; 此时字符串常量池已经有"11"这个对象了,即指向s3引用对象的一个引用。此时我们可以察觉到s3和s4两个引用都是指向了Java Heap中的同一个对象,因此JDK7运行结果为true。
如下代码,分析如s3和s4情形大同小异。

String str = new StringBuilder("awe").append("coder").toString();
System.out.println(str.intern() == str);

假设对示例1代码做些许改动:将s3.intern()移到后一句。

String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4); 

打印结果

jdk7:false

参考前面案例的分析思路,很容易就可以分析出原因。s3语句同上,生成Java Heap对象和字符串常量池"1"对象。执行String s4 = “11”;语句时,池中会新创建"11"对象。再执行s3.intern(),池中"11"对象已经存在。因此s3和s4引用是不同的。

1.5 String#intern()方法的最佳实践

合适地使用String#intern()方法可以节省大量内存空间。不合理使用也会严重影响性能。
重要参考:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

### JVM 字符串常量池工作机制存储方式 #### 1. 基本概念 字符串常量池是一种特殊的运行时常量池,用于优化字符串的存储和比较。它通过减少重复字符串对象的数量来节省内存并提高性能[^3]。 #### 2. 创建过程 当程序中定义了一个字符串字面量时,JVM 首先会在字符串常量池中查找是否存在相同的字符串实例。如果找到,则直接返回该实例的引用;如果没有找到,则会创建一个新的字符串对象,并将其放入字符串常量池中。 #### 3. 存储位置的变化 在 Java 7 及之前版本中,字符串常量池位于永久代(Permanent Generation),而在 Java 8 中为了改进内存管理和解决永久代溢出问题,字符串常量池被迁移到堆中的元空间(Metaspace)[^2]。 #### 4. 动态添加到字符串常量池 除了静态初始化外,还可以通过 `String.intern()` 方法将任意字符串对象手动加入字符串常量池。此方法允许开发者显式控制哪些字符串应该进入常量池[^1]。 #### 5. 初始状态分析 即使应用程序尚未执行任何自定义逻辑,`StringTable` 中可能已包含量条目。这是因为类加载过程中涉及的各种名称(如类名、字段名、方法签名等)均作为字符串常量存入其中[^4]。 #### 示例代码展示如何利用intern()函数实现共享相同内容的不同变量指向同一个地址: ```java public class TestIntern { public static void main(String[] args){ String s1 = new StringBuilder("计算机").append("软件").toString(); System.out.println(s1.intern() == s1); // true String s2 = new StringBuilder("ja").append("va").toString(); System.out.println(s2.intern() == s2); // false, because "java" already exists in the pool. } } ``` 上述例子说明了对于新构建的对象调用 intern 后再做相等判断的结果差异取决于目标值是否预先存在于池内。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值