✅字符串常量是什么时候进入到字符串常量池的?

典型回答

✅字符串常量池是如何实现的?

字符串常量池中的常量有两种来源,一种是字面量会在编译期先进入到Class常量池,然后再在运行期进去到字符串池,还有一种就是在运行期通过intern将字符串对象手动添加到字符串常量池中。

那么,Class常量池中的常量,是在什么时候被放进到字符串池的呢?

Java 的类加载过程要经历加载(Loading)、链接(Linking)、初始化(Initializing)等几个步骤,在链接这个步骤,又分为验证(Verification)、准备(Preparation)以及解析(Resolution)等几个步骤。

在 Java 虚拟机规范及 Java语言规范中都提到过:

《The Java Virtual Machine Specification》 5.4 Linking:

For example, a Java Virtual Machine implementation may choose to resolve each symbolic reference in a class or interface individually when it is used ("lazy" or "late" resolution), or to resolve them all at once when the class is being verified ("eager" or "static" resolution)

《The Java Language Specification》 12.3 Linking of Classes and Interfaces

For example, an implementation may choose to resolve each symbolic reference in a class or interface individually, only when it is used (lazy or late resolution), or to resolve them all at once while the class is being verified (static resolution). This means that the resolution process may continue, in some implementations, after a class or interface has been initialized.

大致意思差不多,就是说,Java 虚拟机的实现可以选择只有在用到类或者接口中的符号引用时才去逐一解析他(延迟解析),或者在验证类的时候就解析每个引用(预先解析)。这意味着在一些虚拟机实现中,把常量放到常量池的步骤可能是延迟处理的。

对于 HotSpot 虚拟机来说,字符串字面量,和其他基本类型的常量不同,并不会在类加载中的解析阶段填充并驻留在字符串常量池中,而是以特殊的形式存储在运行时常量池中。只有当这个字符串字面量被调用时,才会对其进行解析,开始为他在字符串常量池中创建对应的 String 实例。

通过查看 HotSpot JDK 1.8 的 ldc 指令的源代码,也可以验证上面的说法。

ldc 指令表示int、float或String型常量从常量池推送至栈顶

IRT_ENTRY(void, InterpreterRuntime::ldc(JavaThread* thread, bool wide))
  // access constant pool
  ConstantPool* pool = method(thread)->constants();
  int index = wide ? get_index_u2(thread, Bytecodes::_ldc_w) : get_index_u1(thread, Bytecodes::_ldc);
  constantTag tag = pool->tag_at(index);

  assert (tag.is_unresolved_klass() || tag.is_klass(), "wrong ldc call");
  Klass* klass = pool->klass_at(index, CHECK);
    oop java_class = klass->java_mirror();
    thread->set_vm_result(java_class);
IRT_END

所以,字符串常量,是在第一次被调用(准确的说是ldc指令)的时候,进行解析并在字符串池中创建对应的String实例的。

在Java字节码中,ldc(Load Constant)指令用于从当前类的常量池中加载一个int、float或String类型的常量到操作数栈上。这是Java虚拟机(JVM)的一部分,主要用于程序运行时从常量池中提取数据和引用。

在Java代码中使用一个字符串或者数字时,编译器会将其放入常量池中,运行时通过ldc指令将这些常量加载到栈上,以便进行后续的操作或计算。这种机制优化了程序的性能,避免了重复创建相同的字符串或包装类实例。

  • ldc:用于加载int或float常量。
  • ldc_w:扩展版本的ldc,用于加载宽索引的int或float常量,或是一个String常量。
  • ldc2_w:用于加载long和double类型的常量。

原文: https://www.yuque.com/hollis666/xkm7k3/os0m38kyugpxvgsq