目录
-
-
-
- 一、基本概念
-
- [1.1 说明](#1.1 说明)
- [1.2 特点](#1.2 特点)
- 二、存放位置
-
- [2.1 JDK1.6及以前](#2.1 JDK1.6及以前)
- [2.2 JDK1.7](#2.2 JDK1.7)
- [2.3 JDK1.8及以后](#2.3 JDK1.8及以后)
- 三、工作原理
-
- [3.1 创建字符串常量](#3.1 创建字符串常量)
- [3.2 使用new关键字创建字符串](#3.2 使用new关键字创建字符串)
- 四、intern()方法
-
- [4.1 作用](#4.1 作用)
- 五、优点
- 六、字节码分析
-
- [6.1 示例1](#6.1 示例1)
-
- [6.1.1 代码示例](#6.1.1 代码示例)
- [6.1.2 字节码](#6.1.2 字节码)
- [6.1.3 解析](#6.1.3 解析)
- [6.2 示例2](#6.2 示例2)
-
- [6.2.1 代码示例](#6.2.1 代码示例)
- [6.2.2 分析(jdk8)](#6.2.2 分析(jdk8))
- [6.3 示例3](#6.3 示例3)
-
- [6.3.1 代码示例](#6.3.1 代码示例)
- [6.2.2 分析(jdk8)](#6.2.2 分析(jdk8))
-
-
一、基本概念
1.1 说明
- 1.JVM字符串常量池是Java虚拟机(JVM)中一个特殊的内存区域。
- 2.JVM字符串常量池用于存储字符串常量。
- 3.提高性能和减少内存开销。
- 4.字符串常量池是JVM用于存储字符串常量的一个内存区域,避免了相同字符串的重复创建,节省内存空间。
1.2 特点
- 1.字符串常量池中的字符串对象是不可变的。
- 2.相同的字符串常量在池中只存储一份,通过引用共享。
二、存放位置
2.1 JDK1.6及以前
- 1.字符串常量池存放在永久代中,永久代是非堆内存的一部分,用于存储类的元数据、常量、静态变量等。
2.2 JDK1.7
- 1.字符串常量池从永久代移动到了Java堆中,而运行时常量池保留在永久代中。
- 2.这一变化为了适应永久代内存限制问题,并提升性能。
2.3 JDK1.8及以后
- 1.永久代被移除,取而代之的是元空间,字符串常量池仍然位于Java堆中。
- 2.运行时常量池被移动到元空间。
三、工作原理
3.1 创建字符串常量
- 1.使用双引号创建字符串时(String a = "123"; ),JVM会首先在字符串常量池中查找是否已存在该字符串。
- 2.如果存在,则直接返回池中该字符串的引用。
- 3.如果不存在,则在常量池中创建该字符串的实例,并返回其引用。
3.2 使用new关键字创建字符串
- 1.使用new关键字创建字符串对象(如String str = new String("abc");)时,JVM会在堆内存中创建一个新的字符串对象,而不管字符串常量池中是否已存在相同的字符串。
- 2.如果需要,可以通过调用intern()方法将新创建的字符串对象放入常量池中。
四、intern()方法
4.1 作用
- 1.intern()方法是String类的一个本地方法。
- 2.用于将字符串对象添加到字符串常量池中。
- 3.如果常量池中已经包含了一个等于此String对象的字符串(使用equals(Object)方法确定),则返回代表池中这个字符串的String对象的引用。
- 4.否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。
五、优点
- 1.节省内存:通过共享相同的字符串常量,避免了不必要的重复创建。
- 2.提高性能:减少了对象创建和垃圾回收的开销。
- 3.简化字符串比较:由于字符串常量池中的字符串是唯一的,可以使用==操作符来比较字符串的引用,从而简化比较操作。
六、字节码分析
6.1 示例1
6.1.1 代码示例
@Test
public void test(){
String str1 = new String("hello") + new String("world");
String str2 = "helloworld";
System.out.println(str1 == str2);
}
6.1.2 字节码
0 new #2 <java/lang/StringBuilder>
3 dup
4 invokespecial #3 <java/lang/StringBuilder.<init> : ()V>
7 new #4 <java/lang/String>
10 dup
11 ldc #5 <hello>
13 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 new #4 <java/lang/String>
22 dup
23 ldc #8 <world>
25 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
34 astore_1
35 ldc #10 <helloworld>
37 astore_2
38 getstatic #11 <java/lang/System.out : Ljava/io/PrintStream;>
41 aload_1
42 aload_2
43 if_acmpne 50 (+7)
46 iconst_1
47 goto 51 (+4)
50 iconst_0
51 invokevirtual #12 <java/io/PrintStream.println : (Z)V>
54 return
6.1.3 解析
-
- 0 new #2 <java/lang/StringBuilder>: 调用StringBuilder的new方法
-
- 3 dup:复制操作数栈栈顶的一个字(通常是对象引用或数据类型值),并将这个字重新压入栈顶。
-
- 4 invokespecial #3 <java/lang/StringBuilder. : ()V>:执行StringBuilder的初始化方法,会消耗操作数栈顶一个字。
-
- 7 new #4 <java/lang/String>:new一个String对象,对象的引用压入操作数栈。
-
- 10 dup:复制操作数栈栈顶的一个字(通常是对象引用或数据类型值),并将这个字重新压入栈顶。
-
- 11 ldc #5 :加载栈顶的一个字,即hello。
-
- 13 invokespecial #6 <java/lang/String. : (Ljava/lang/String;)V>:初始化String,消耗一个string对象的引用和复制的字。
-
- 16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> : append操作,将hello追加进来。
-
- 19 new #4 <java/lang/String> : new一个String对象,对象的引用压入操作数栈。
-
- 22 dup:复制操作数栈栈顶的一个字(通常是对象引用或数据类型值),并将这个字重新压入栈顶。
-
- 23 ldc #8 : 加载栈顶的一个字,即world。
-
- 25 invokespecial #6 <java/lang/String. : (Ljava/lang/String;)V>:初始化String,消耗一个string对象的引用和复制的字。
-
- 28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>: append操作,将world追加进来。
-
- 31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;> :调用StringBuilder的toString方法
-
- StringBuilder的toString方法中是直接new了一个String对象。
-
- 而String str2 = "helloworld";是常量池的引用。
-
- 因此不是同一个内存地址,所以结果是false。
6.2 示例2
6.2.1 代码示例
@Test
public void test(){
String str1 = new String("hello") + new String("world");
str1.intern();
String str2 = "helloworld";
System.out.println(str1 == str2);
}
6.2.2 分析(jdk8)
- 1.str1是直接new了一个对象,执行intern()方法后,将字符串对象添加到字符串常量池中。
- 2.String str2 = "helloworld";会先在字符串常量池中找是否有,如果有则返回其对象的引用。
- 3.所以结果是true。
6.3 示例3
6.3.1 代码示例
@Test
public void test(){
String str1 = new String("helloworld") ;
String str2 = "helloworld";
String intern = str1.intern();
System.out.println(str1 == str2);
System.out.println(str1 == intern);
System.out.println(str2 == intern);
}
6.2.2 分析(jdk8)
- 1.str1在堆上new了一个string对象。
- 2.str2是将字面量"helloworld"放入字符串常量池中。
- 3.str1调用intern方法,判断字符串常量池中有没有helloworld,发现有,返回了该字符串常量池的引用即str2。
- 4.此时str1不等于str2。str2和intern是相等的。