不再为 String 创建对象而烦恼!一文读懂底层原理
Java 中 String
对象的创建过程涉及多个步骤,包括字符串字面量的处理、new String()
的实例化、字符串常量池的管理等。以下是详细的分析,涵盖从 Java 代码到 JVM 内部的完整过程。
1. 字符串字面量的创建
字符串字面量(如 "hello"
)是 Java 中最常见的字符串创建方式。它的创建过程如下:
编译阶段的实现
在编译阶段,编译器(如 javac
)会进行以下操作:编译器会将字符串字面量(如 "hello"
)存储到类文件的常量池(Constant Pool)中;编译器会生成字节码指令,用于在运行时从常量池中加载字符串字面量。对于 String s = "hello";
,编译器会生成类似以下的字节码:
在编译后的类文件中,字符串字面量 "hello"
会被存储为常量池中的一个条目。例如:
#2
是字符串常量池中的一个条目,指向#21
。#21
是一个Utf8
条目,存储了字符串的实际内容("hello"
)。
运行阶段的实现
当类被加载到 JVM 时,JVM 会解析类文件的常量池,并将字符串字面量加载到 JVM 的字符串常量池(String Pool)中。
解析常量池:
JVM 会读取类文件中的常量池,找到所有的字符串字面量(如 "hello"
)。对于每个字符串字面量,JVM 会检查字符串常量池中是否已经存在相同内容的字符串。
加载到字符串常量池:
如果字符串常量池中不存在相同内容的字符串,JVM 会创建一个新的 String
对象,并将其添加到字符串常量池中。如果已经存在,则直接使用池中的字符串引用。
当代码执行到字符串字面量的引用时,JVM 会从字符串常量池中获取对应的字符串对象。
字节码指令的执行:
对于 ldc #2
指令,JVM 会从常量池中加载 #2
对应的字符串对象。如果字符串常量池中已经存在 "hello"
,则直接返回其引用。如果不存在,则创建新的 String
对象并添加到字符串常量池中。
2. new String()
的创建
使用 new String()
创建字符串对象时,会显式地在堆内存中创建一个新的对象。
编译器的处理
在编译阶段,编译器(如 javac
)会进行以下操作:
处理字符串字面量:编译器会将字符串字面量
"hello"
存储到类文件的常量池(Constant Pool)中。常量池中的字符串字面量会在类加载时被加载到 JVM 的字符串常量池(String Pool)中。生成字节码指令:编译器会生成字节码指令,用于在运行时创建
String
对象。对于String s = new String("hello");
,编译器会生成类似以下的字节码:
在编译后的类文件中,字符串字面量 "hello"
会被存储为常量池中的一个条目。例如:
#2
是字符串常量池中的一个条目,指向 #21
。#21
是一个 Utf8
条目,存储了字符串的实际内容("hello"
)。#3
是 String
类的构造方法引用。
运行阶段的实现
当类被加载到 JVM 时,JVM 会解析类文件的常量池,并将字符串字面量加载到 JVM 的字符串常量池(String Pool)中。当代码执行到 new String()
时,JVM 会进行以下操作:
加载字符串字面量:执行 ldc #2
指令,从常量池中加载字符串字面量 "hello"
的引用。如果字符串常量池中已经存在 "hello"
,则直接返回其引用;如果不存在,则创建新的 String
对象并添加到字符串常量池中。
创建新的 String
对象:执行 invokespecial #3
指令,调用 String
类的构造方法。当 JVM 执行 new String()
时,会调用 java_lang_String::create_from_string()
方法来创建 String
对象。
create_oop_from_unicode()
方法用于创建一个新的 String
对象,并将其内容初始化为指定的字符数组。
allocate_instance()
:从 JVM 的堆内存分配一个新的String
对象。new_charArray()
:创建一个新的字符数组(char[]
),用于存储字符串内容。set_value()
:将字符数组赋值给String
对象的value
字段。
3. 两种创建方式的特性
字符串字面量的创建和 new String()
的创建在 Java 中有一些相同点,但也有显著的差异。
相同点
内容不可变性:两种方式创建的字符串对象都是不可变的,即一旦创建,字符串的内容就无法被更改。这是 Java 字符串设计的一个重要特性,有助于提升性能及安全性。
数据类型相同:无论是通过字符串字面量创建还是通过
new String()
创建,最终得到的对象都是String
类型。
差异点
内存分配不同:当使用字符串字面量创建字符串时,JVM 会首先检查字符串常量池中是否已经存在与该字面量内容相同的字符串对象。如果存在,则直接返回该对象的引用;如果不存在,则在堆中创建一个新的字符串对象,并将其引用放入字符串常量池中。每次使用
new String()
创建字符串时,无论字符串常量池中是否存在相同内容的字符串对象,都会在堆中创建一个新的字符串对象对象引用不同:如果两个变量使用了相同的字符串字面量赋值,那么这两个变量实际上引用的是同一个字符串对象。即使两个变量使用了相同的字符串内容来调用
new String()
,它们也会引用不同的字符串对象性能开销不同:由于可能复用已有的字符串对象,因此在某些情况下可以减少内存开销和提高性能。new String():由于每次都会创建新的字符串对象,因此在内存开销上可能相对较大。
通过理解两者的相同点和差异点,可以更好地选择适合的字符串创建方式,优化内存使用和性能。
4. 代码案例
字符串字面量
new String()
new String()
与intern()
版权声明: 本文为 InfoQ 作者【储诚益】的原创文章。
原文链接:【http://xie.infoq.cn/article/d02b761f590bceb31ad72c323】。文章转载请联系作者。
评论