编译器对字符串字面量的行为以创建字符串常量池

3

阅读这些讨论后 - 问题1, 问题2, 文章

我对Java字符串常量池有以下理解(如果我错了,请纠正):

当源代码被编译时,编译器会查找程序中所有的字符串字面量(放在双引号中的字符串),并在堆区域创建不同的对象(无重复项),并在特殊的内存区域中维护它们的引用,称为字符串常量池(方法区内的一个区域)。任何其他字符串对象都是在运行时创建的。

假设我们的代码有以下语句:

String a = "abc";                  //Line 1
String b = "xyz";                  //Line 2
String c = "abc";                  //Line 3
String d = new String("abc"):      //Line 4

当上述代码被编译时,
第1行:在堆中创建了一个字符串对象 "abc",并且该对象被变量 a 和字符串常量池所引用。

enter image description here

第二行:编译器在字符串常量池中搜索是否存在指向对象“xyz”的引用。但没有找到。因此,它创建了对象“xyz”并将其引用放入字符串常量池。

enter image description here

第三行:这次编译器在字符串常量池中找到对象,并且不会在池或堆中进行任何额外的条目。变量c只是引用现有的对象,该对象也被a引用。

enter image description here

第四行:第四行中的文字已经存在于字符串常量池中,因此不会再次添加到池中。然而,在运行时,另一个字符串对象被创建并存储在变量d中。

enter image description here

现在我有以下问题/疑问:

  1. 上述描述的是否完全准确?
  2. 编译器如何创建对象?据我所知,对象是在运行时创建的,堆是一个运行时内存区域。那么,在编译时如何以及在哪里创建字符串对象!
  3. 源代码可以在一台机器上编译并在另一台机器上运行。或者,即使在同一台机器上编译和运行,它们也可以在不同的时间编译和运行。那么,如何恢复这些(在编译时创建的)对象?
  4. 当我们对一个字符串进行国际化处理时会发生什么。

当然,字符串对象在不同的机器上并不相同,但它们在某个JVM中是相同的。字符串常量池并不是在源代码编译时创建的。请参考:https://dev59.com/IG445IYBdhLWcg3wUoxe - Salem
你能详细说明一下吗?如果你知道答案,请尽可能详细地解释。链接的问题只提供了关于字符串池的信息,但没有说明常量池是如何创建的。@Mango - Deb
1个回答

2
  1. 上述描述的是否准确无误?

是的,概念上是准确的,但常量池和字符串池是两个不同的概念。

常量池.class文件的一部分,包含该类中使用的所有常量。

字符串池是运行时的概念 - 存储在这里的是被interned的字符串和字符串字面值。这里有更多信息

这里是关于常量池的JVM规范。它是关于.class格式的部分。

  1. 编译器如何创建对象?据我所知,对象是在运行时创建的,堆是一个运行时内存区域。那么,在编译时如何以及在哪里创建字符串对象!

我认为,这个问题的具体解释和实现取决于JVM(如果我错了,请纠正我),但基本的解释是:无论何时JVM决定加载一个类,任何在常量池中发现的字符串都会自动放置到运行时字符串池中,并且任何重复的字符串都会指向同一个实例。

在其中一个链接答案的评论中,Paŭlo Ebermann说

当VM加载类时,字符串常量将被复制到堆中,到一个VM-wide字符串池

因此,这似乎至少是Sun的VM实现字符串池的方式。

在JDK 7 / HotSpot之前,interned字符串存储在永久代空间中 - 现在它们存储在主堆中。

  1. 源代码可以在一台机器上编译,在另一台机器上运行。或者,甚至可以在同一台机器上编译并在不同的时间运行。那么,那些(在编译时创建的)对象是如何恢复的?

常量存储在编译后的文件中。因此,只要JVM决定加载该类,就可以检索它们。

  1. 将字符串intern化会发生什么。

这在这里有解答:

对一系列字符串执行String.intern()将确保具有相同内容的所有字符串共享相同的内存。


如果我理解正确,字符串字面量与其他数据一样存储在.class文件中。当JVM加载该类时,它将这些字符串数据存储在字符串池中。在加载下一个类时,它只会将不存在的字符串放入池中。但是,https://dev59.com/1nI-5IYBdhLWcg3wYnSQ#1881936 这个答案说“Java编译器足够聪明,可以使……”这是什么意思?如果这是由JVM完成的,编译器如何参与其中? - Deb
@Deb 我认为这是一个措辞不当 - 编译器所做的只是将字符串放入常量池中,并使用该常量代替对 new String(...) 的调用。JVM 负责将 interned 字符串放入堆中。 - Salem
1
@Deb,“abc”在_binary_中将存储在两个.class文件中,但一旦加载了这两个类,(运行时)字符串池将只包含一个“abc”字符串。对“abc”的两个引用都将指向此一个字符串。 - Salem
1
@Deb:如果一个类包含多个相同的字符串常量,例如“abc”,它们通常会被编译成类常量池中的单个条目。规范并不要求这样做,但所有常用的编译器都这样做,因为它可以缩短类文件并减少运行时的工作量。 - Holger
请注意,Paŭlo Ebermann 的说法是错误的。在广泛使用的 HotSpot JVM 中(即 Sun 以前开发的 JVM),表示常量的 String 实例是在第一次实际使用时创建的,而不是在类加载时创建的,这可以通过 此答案 证明。原则上,在类加载时创建它们可能是一种有效的策略,也许 HotSpot 很久以前就这样做了... - Holger
显示剩余2条评论

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