一个字符串字面量存储在堆栈上吗?一个新的字符串存储在堆栈上吗?

30

可能是重复问题:
String对象和字符串字面值之间的区别

假设我有两个语句:

String one = "abc";
String two = new String("abc");

哪个是栈内存,哪个是堆内存?

它们的区别是什么?

有多少对象被创建以及在内存中如何引用?

最佳实践是什么?


这取决于你在哪里声明它们。 - ironwood
这个问题之前已经讨论过了...请参考给定的链接https://dev59.com/bXA75IYBdhLWcg3wbojM - AurA
通常情况下,无法知道任何对象是否存储在堆栈上。请参见https://dev59.com/jXE85IYBdhLWcg3waCxT。 - Raedwald
4个回答

29

所有对象都存储在堆上(包括它们字段的值)。1

局部变量(包括参数)始终包含原始值或引用,并存储在堆栈上。1

因此,对于您的两行代码:

String one = "abc";
String two = new String("abc");

如果onetwo是局部变量,那么在堆上会有两个对象(包含"abc"的两个String对象),同时在栈上会有两个引用,每个对象都有一个引用。

实际上,对于像字符串字面量这样的池化字符串,它们存储在所谓的字符串池中。

创建了多少个对象,以及在内存中引用是如何处理的?

有趣的是你问了这个问题,因为在Java语言中,String是特殊的。

但有一件事是可以保证的:每当你使用new时,你确实会获得一个新的引用。这意味着two将不会引用与one相同的对象,这意味着在那两行代码后,你将在堆上拥有两个对象。


1) 严格来说,Java语言规范没有指定值在内存中的存储方式或位置。然而,在实践中,通常是这样做的。


8
好答案。但实际情况不再那么简单了。JVM可能会进行逃逸分析并决定直接在堆栈上分配对象,这意味着当方法结束时清理它们是免费的。但这是一种高级优化,通常不需要了解。 - Jesper
当你说“包括它们的属性”时,属性是指类成员字段吗? - jmrah
是的。答案已更新。 - aioobe

9
第一个称为“字符串字面量”,在程序编译时创建,而第二个是“字符串对象”,在运行时创建。
由于在第二种情况下使用了“new”关键字,因此它被分配在堆中。
在第一种情况下,对象是通过称为“内部化”的机制创建的。当您尝试创建表示相同字符序列的另一个字符串字面量时,编译器将引用先前创建并存储在字符串池中的字符串。

new String("abc") 会在堆中创建字符串对象,因为 new 关键字会在堆中创建对象。而 String str = "abc" 则会在字符串池中创建字符串字面量,字符串池位于永久代空间中。 - Akash5288

5
只有原始类型 (例如 int,long 等) 的实例被保存在堆栈上。所有引用类型的实例 (StringIntegerLongYourTypeHere,...) 都保存在堆上。 更新:如评论所指出,对于引用类型 (即非原始类型 - Object 及其子类) 的实例引用可以保存在堆栈上。这些是您的本地变量。
这不是“最佳实践”,而是 JVM 的工作方式,您无法更改它。

4
不,引用并没有被保存在堆内存中,对象才是。 - aioobe
@aioobe 修正了措辞,使用“...类型的实例”来澄清这一点。 - Victor Sorokin
2
Victor,你的回答仍然是误导性的。引用类型变量(String a中的a)的值很可能存储在堆栈上。但是它所指向的对象存储在堆上。 - aioobe
3
同意。如果原始值是类的字段,它们可以在堆上分配,因为它们将与它们所属的对象一起存储。使用关键字“new”创建的内容会进入堆。栈值仅存在于创建它们的方法的范围内。 - dgt

3
在您的情况下,将创建2个String对象。 通常,所有对象都在堆上创建。但是由于String one是一个字符串字面量,因此它将存储在字符串池中(在PermGen中)。您还可以使用intern()方法将字符串添加到字符串池中并获取对其的引用。
如果您发布的声明在方法中,则引用将存储在堆栈上,但不包括对象本身。
至于最佳实践,我认为应该是: String one="abc"
这有两个原因: 1.代码更清晰 2.经过国际化的字符串可以使用==进行比较,这比equals更快。但是,您需要在比较之前intern()任何非文字字符串。
编辑: 您可能会对查看此链接感兴趣:Java SE 7中的逃逸分析。它介绍了一些影响对象分配的HotSpot相关优化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接