深入jvm理解字符串常量池

深入jvm理解字符串常量池

精选文章moguli202025-05-14 15:35:135A+A-


困惑

废话不多说,首先看一段代码,看看你能猜对其中的几个结果

/**
 * @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")的时候不直接返回字符串常量池中的对象,这样不是更能节省空间吗,今天太晚了,有时间我会在评论区中回答或者补充一下这篇文章。

点击这里复制本文地址 以上内容由莫古技术网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

莫古技术网 © All Rights Reserved.  滇ICP备2024046894号-2