深入jvm理解字符串常量池
困惑
废话不多说,首先看一段代码,看看你能猜对其中的几个结果
/**
* @author hanhai
* @version 1.0.0
* @Description 字符串常量池、java8中s1.intern()先在字符串常量池中查找把常量池中的地址返回,如果字符串常量池中不存在则把堆中的
* 字符串地址复制到常量池中,然后将常量池地址返回
* @email 495019733@qq.com
* @date 2023/8/22 23:41
*/
public class StringTableLearn {
public static void main(String[] args) {
String a = new String(new char[]{'a', 'b', 'c'});
String b = new String(new char[]{'a', 'b', 'c'});
String c = new String("abc");
String aIntern = a.intern();
String bIntern = b.intern();
String cIntern = c.intern();
System.out.println(a == aIntern);
System.out.println(b == bIntern);
System.out.println(c == cIntern);
System.out.println(aIntern == cIntern);
System.out.println(bIntern == cIntern);
}
}
运行结果
D:\jdk-21\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:55014,suspend=y,server=n -javaagent:C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2023.3\captureAgent\debugger-agent.jar=file:/C:/Users/Administrator/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath "D:\workspace\java-learn\target\classes;D:\mavenRepo\org\springframework\boot\spring-boot-starter-aop\3.2.1\spring-boot-starter-aop-3.2.1.jar;D:\mavenRepo\org\springframework\spring-aop\6.1.2\spring-aop-6.1.2.jar;D:\mavenRepo\org\springframework\spring-beans\6.1.2\spring-beans-6.1.2.jar;D:\mavenRepo\org\aspectj\aspectjweaver\1.9.21\aspectjweaver-1.9.21.jar;D:\mavenRepo\com\google\protobuf\protobuf-java\3.25.3\protobuf-java-3.25.3.jar;D:\mavenRepo\com\lmax\disruptor\3.4.2\disruptor-3.4.2.jar;D:\mavenRepo\org\springframework\boot\spring-boot-starter-webflux\3.2.1\spring-boot-starter-webflux-3.2.1.jar;D:\mavenRepo\org\springframework\boot\spring-boot-starter-json\3.2.1\spring-boot-starter-json-3.2.1.jar;D:\mavenRepo\com\fasterxml\jackson\core\jackson-databind\2.15.3\jackson-databind-2.15.3.jar;D:\mavenRepo\com\fasterxml\jackson\core\jackson-annotations\2.15.3\jackson-annotations-2.15.3.jar;D:\mavenRepo\com\fasterxml\jackson\core\jackson-core\2.15.3\jackson-core-2.15.3.jar;D:\mavenRepo\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.15.3\jackson-datatype-jdk8-2.15.3.jar;D:\mavenRepo\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.15.3\jackson-datatype-jsr310-2.15.3.jar;D:\mavenRepo\com\fasterxml\jackson\module\jackson-module-parameter-names\2.15.3\jackson-module-parameter-names-2.15.3.jar;D:\mavenRepo\org\springframework\boot\spring-boot-starter-reactor-netty\3.2.1\spring-boot-starter-reactor-netty-3.2.1.jar;D:\mavenRepo\io\projectreactor\netty\reactor-netty-http\1.1.14\reactor-netty-http-1.1.14.jar;D:\mavenRepo\io\projectreactor\netty\reactor-netty-core\1.1.14\reactor-netty-core-1.1.14.jar;D:\mavenRepo\org\springframework\spring-web\6.1.2\spring-web-6.1.2.jar;D:\mavenRepo\io\micrometer\micrometer-observation\1.12.1\micrometer-observation-1.12.1.jar;D:\mavenRepo\io\micrometer\micrometer-commons\1.12.1\micrometer-commons-1.12.1.jar;D:\mavenRepo\org\springframework\spring-webflux\6.1.2\spring-webflux-6.1.2.jar;D:\mavenRepo\io\projectreactor\reactor-core\3.6.1\reactor-core-3.6.1.jar;D:\mavenRepo\org\reactivestreams\reactive-streams\1.0.4\reactive-streams-1.0.4.jar;D:\mavenRepo\org\springframework\boot\spring-boot-starter\3.2.1\spring-boot-starter-3.2.1.jar;D:\mavenRepo\org\springframework\boot\spring-boot\3.2.1\spring-boot-3.2.1.jar;D:\mavenRepo\org\springframework\spring-context\6.1.2\spring-context-6.1.2.jar;D:\mavenRepo\org\springframework\spring-expression\6.1.2\spring-expression-6.1.2.jar;D:\mavenRepo\org\springframework\boot\spring-boot-autoconfigure\3.2.1\spring-boot-autoconfigure-3.2.1.jar;D:\mavenRepo\org\springframework\boot\spring-boot-starter-logging\3.2.1\spring-boot-starter-logging-3.2.1.jar;D:\mavenRepo\ch\qos\logback\logback-classic\1.4.14\logback-classic-1.4.14.jar;D:\mavenRepo\ch\qos\logback\logback-core\1.4.14\logback-core-1.4.14.jar;D:\mavenRepo\org\apache\logging\log4j\log4j-to-slf4j\2.21.1\log4j-to-slf4j-2.21.1.jar;D:\mavenRepo\org\apache\logging\log4j\log4j-api\2.21.1\log4j-api-2.21.1.jar;D:\mavenRepo\org\slf4j\jul-to-slf4j\2.0.9\jul-to-slf4j-2.0.9.jar;D:\mavenRepo\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;D:\mavenRepo\org\springframework\spring-core\6.1.2\spring-core-6.1.2.jar;D:\mavenRepo\org\springframework\spring-jcl\6.1.2\spring-jcl-6.1.2.jar;D:\mavenRepo\org\yaml\snakeyaml\2.2\snakeyaml-2.2.jar;D:\mavenRepo\org\projectlombok\lombok\1.18.30\lombok-1.18.30.jar;D:\mavenRepo\org\slf4j\slf4j-api\2.0.9\slf4j-api-2.0.9.jar;D:\mavenRepo\org\openjdk\jol\jol-core\0.16\jol-core-0.16.jar;D:\mavenRepo\org\ow2\asm\asm-util\8.0\asm-util-8.0.jar;D:\mavenRepo\org\ow2\asm\asm\8.0\asm-8.0.jar;D:\mavenRepo\org\ow2\asm\asm-tree\8.0\asm-tree-8.0.jar;D:\mavenRepo\org\ow2\asm\asm-analysis\8.0\asm-analysis-8.0.jar;D:\mavenRepo\io\netty\netty-all\4.1.106.Final\netty-all-4.1.106.Final.jar;D:\mavenRepo\io\netty\netty-buffer\4.1.104.Final\netty-buffer-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec\4.1.104.Final\netty-codec-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-dns\4.1.104.Final\netty-codec-dns-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-haproxy\4.1.104.Final\netty-codec-haproxy-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-http\4.1.104.Final\netty-codec-http-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-http2\4.1.104.Final\netty-codec-http2-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-memcache\4.1.104.Final\netty-codec-memcache-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-mqtt\4.1.104.Final\netty-codec-mqtt-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-redis\4.1.104.Final\netty-codec-redis-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-smtp\4.1.104.Final\netty-codec-smtp-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-socks\4.1.104.Final\netty-codec-socks-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-stomp\4.1.104.Final\netty-codec-stomp-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-codec-xml\4.1.104.Final\netty-codec-xml-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-common\4.1.104.Final\netty-common-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-handler\4.1.104.Final\netty-handler-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport-native-unix-common\4.1.104.Final\netty-transport-native-unix-common-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-handler-proxy\4.1.104.Final\netty-handler-proxy-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-handler-ssl-ocsp\4.1.104.Final\netty-handler-ssl-ocsp-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-resolver\4.1.104.Final\netty-resolver-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-resolver-dns\4.1.104.Final\netty-resolver-dns-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport\4.1.104.Final\netty-transport-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport-rxtx\4.1.104.Final\netty-transport-rxtx-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport-sctp\4.1.104.Final\netty-transport-sctp-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport-udt\4.1.104.Final\netty-transport-udt-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport-classes-epoll\4.1.104.Final\netty-transport-classes-epoll-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport-classes-kqueue\4.1.104.Final\netty-transport-classes-kqueue-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-resolver-dns-classes-macos\4.1.104.Final\netty-resolver-dns-classes-macos-4.1.104.Final.jar;D:\mavenRepo\io\netty\netty-transport-native-epoll\4.1.104.Final\netty-transport-native-epoll-4.1.104.Final-linux-x86_64.jar;D:\mavenRepo\io\netty\netty-transport-native-epoll\4.1.104.Final\netty-transport-native-epoll-4.1.104.Final-linux-aarch_64.jar;D:\mavenRepo\io\netty\netty-transport-native-epoll\4.1.104.Final\netty-transport-native-epoll-4.1.104.Final-linux-riscv64.jar;D:\mavenRepo\io\netty\netty-transport-native-kqueue\4.1.104.Final\netty-transport-native-kqueue-4.1.104.Final-osx-x86_64.jar;D:\mavenRepo\io\netty\netty-transport-native-kqueue\4.1.104.Final\netty-transport-native-kqueue-4.1.104.Final-osx-aarch_64.jar;D:\mavenRepo\io\netty\netty-resolver-dns-native-macos\4.1.104.Final\netty-resolver-dns-native-macos-4.1.104.Final-osx-x86_64.jar;D:\mavenRepo\io\netty\netty-resolver-dns-native-macos\4.1.104.Final\netty-resolver-dns-native-macos-4.1.104.Final-osx-aarch_64.jar;D:\JetBrains\IntelliJ IDEA 2022.3.3\lib\idea_rt.jar" com.hanhai.jvm.str.StringTableLearn
Connected to the target VM, address: '127.0.0.1:55014', transport: 'socket'
false
false
false
true
true
Disconnected from the target VM, address: '127.0.0.1:55014', transport: 'socket'
Process finished with exit code 0
看完了是不是很困惑,要理解这段代码首先需要理解一个概念字符串常量池(String Pool)。
字符串常量池
字符串常量池是 Java 中用于高效管理字符串对象的一块特殊内存区域,下面从多个维度详细介绍它。
概念
字符串常量池的主要目的是避免字符串对象的重复创建,节省内存空间并提高性能。当程序中使用字符串字面量时,JVM 会先检查字符串常量池中是否已存在相同内容的字符串。若存在,则直接返回该字符串的引用;若不存在,则在常量池中创建新的字符串对象并返回其引用。
存储位置变迁
JDK 6 及以前:位于方法区(永久代),此区域大小固定,容易因创建过多字符串而导致内存溢出。
JDK 7 及以后:移至 Java 堆中,由于堆内存可动态扩展,减少了内存溢出的风险。
字符串创建与常量池的交互
1. 字符串字面量创建
/**
* @author hanhai
* @version 1.0.0
* @Description 字符串常量池、java8中s1.intern()先在字符串常量池中查找把常量池中的地址返回,如果字符串常量池中不存在则把堆中的
* 字符串地址复制到常量池中,然后将常量池地址返回
* @email 495019733@qq.com
* @date 2023/8/22 23:41
*/
public class StringTableLearn {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // 输出 true
}
}
上述代码中,s1 和 s2 都使用字符串字面量 "hello" 初始化。JVM 首次遇到 "hello" 时(划重点,这个后续会讲到),会在常量池中创建该字符串,s1 指向该对象;创建 s2 时,发现常量池已有 "hello",则直接返回该引用,所以 s1和 s2 指向同一对象。
2. new 关键字创建
/**
* @author hanhai
* @version 1.0.0
* @Description 字符串常量池、java8中s1.intern()先在字符串常量池中查找把常量池中的地址返回,如果字符串常量池中不存在则把堆中的
* 字符串地址复制到常量池中,然后将常量池地址返回
* @email 495019733@qq.com
* @date 2023/8/22 23:41
*/
public class StringTableLearn {
public static void main(String[] args) {
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3 == s4); // 输出 false
System.out.println(s3 == "hello"); // 输出 false
}
}
3、intern()方法
源码如下,是一个native方法,会在首次执行时检查字符串常量池中是否有字符串对象equals这个字符串字面量,如果有则返回字符串常量池中的常亮,如果没有则将此字符串对象添加到字符串常量池中(添加的是这个对象的引用),并返回对此String对象的引用。
/**
* 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 {@jls 3.10.5} of the
* <cite>The Java 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();
好了,有了这些理论知识之后,我们再来解释文章开头的代码,
首先String a = new String(new char[]{'a', 'b', 'c'});在堆中创建一个对象a,
String b = new String(new char[]{'a', 'b', 'c'});在堆中创建一个对象b,
String c = new String("abc");在字符串常量池中创建一个对象,并在堆中创建一个对象c
String aIntern = a.intern();在字符串常量池中查找字符串对象,已经有了,于是aIntern指向字符串常量池中的常量,所以对照着比较一下我们就有答案了。
好了,那么我们把代码调整一下再看看结果
public class StringTableLearn {
public static void main(String[] args) {
String a = new String(new char[]{'a', 'b', 'c'});
String b = new String(new char[]{'a', 'b', 'c'});
String aIntern = a.intern();
String c = new String("abc");
String bIntern = b.intern();
String cIntern = c.intern();
System.out.println(a == aIntern);
System.out.println(b == bIntern);
System.out.println(c == cIntern);
System.out.println(aIntern == cIntern);
System.out.println(bIntern == cIntern);
}
}
运行代码看到结果
Connected to the target VM, address: '127.0.0.1:52003', transport: 'socket'
true
false
false
true
true
Disconnected from the target VM, address: '127.0.0.1:52003', transport: 'socket'
Process finished with exit code 0
奇怪了,我们仅仅是调整了一下
String aIntern = a.intern();
String c = new String("abc");
这两行的代码a == aIntern变为true了,这是怎么回事呢,其实前面解释intern()方法的时候已经说了,String aIntern = a.intern();的时候字符串常量池中还没有这个字符串对象,于是就在字符串常量池中创建这个对象的引用,aIntern就指向堆中的引用。
最后留一个问题
为什么String a = "abc" , String c = new String("abc");为什么字符串常量池中已经有了这个字符串对象,为什么new String("abc")的时候不直接返回字符串常量池中的对象,这样不是更能节省空间吗,今天太晚了,有时间我会在评论区中回答或者补充一下这篇文章。