Java HotSpot vm 中 String Literal 加载到 StringTable 的时间

2023-12-20

当我学习 java.lang.String Java API 时,这个问题就出现了。

我找到了一篇中文文章。Java 中new String("字面量") 中 "字面量" 是何时进入字符串常量池的? https://www.zhihu.com/question/55994121/answer/147296098

it said,CONSTANT_String在 HotSpot VM 中是惰性解析,因此 String Literal 被加载到 StringTable util 中使用。

我发现了一些相关的说法。

jvms 第 5.4 章。链接 http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4 says

例如,Java 虚拟机实现可以选择在使用时单独解析类或接口中的每个符号引用(“惰性”或“延迟”解析),或者在验证类时一次性解析所有符号引用( “热切”或“静态”解决方案)。

我找到了一些openjdk代码ldc

IRT_ENTRY(void, InterpreterRuntime::ldc(JavaThread* thread, bool wide))  
  // access constant pool  
  constantPoolOop 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);  

  if (tag.is_unresolved_klass() || tag.is_klass()) {  
    klassOop klass = pool->klass_at(index, CHECK);  
    oop java_class = klass->java_mirror();  
    thread->set_vm_result(java_class);  
  } else {  
#ifdef ASSERT  
    // If we entered this runtime routine, we believed the tag contained  
    // an unresolved string, an unresolved class or a resolved class.  
    // However, another thread could have resolved the unresolved string  
    // or class by the time we go there.  
    assert(tag.is_unresolved_string()|| tag.is_string(), "expected string");  
#endif  
    oop s_oop = pool->string_at(index, CHECK);  
    thread->set_vm_result(s_oop);  
  }  
IRT_END  

以及关于 pool->string_at(index, CHECK) 的代码

oop constantPoolOopDesc::string_at_impl(constantPoolHandle this_oop, int which, TRAPS) {  
  oop str = NULL;  
  CPSlot entry = this_oop->slot_at(which);  
  if (entry.is_metadata()) {  
    ObjectLocker ol(this_oop, THREAD);  
    if (this_oop->tag_at(which).is_unresolved_string()) {  
      // Intern string  
      Symbol* sym = this_oop->unresolved_string_at(which);  
      str = StringTable::intern(sym, CHECK_(constantPoolOop(NULL)));  
      this_oop->string_at_put(which, str);  
   } else {  
      // Another thread beat us and interned string, read string from constant pool  
     str = this_oop->resolved_string_at(which);  
    }  
  } else {  
    str = entry.get_oop();  
  }  
  assert(java_lang_String::is_instance(str), "must be string");  
  return str;  
}  

But

这些代码只能证明 String Literal 可能已加载到 StringTable util 中ldc,但无法证明 HotSpot VM 中的延迟解析。

有人可以明确解释一下吗。

仅供参考,我懂一点c,但不懂c++。

Thanks.!


有一个极端的情况,允许在测试之前在 Java 应用程序中检查字符串是否存在于池中,但每个字符串只能执行一次。与相同内容的字符串文字一起,可以检测到延迟加载:

public class Test {
    public static void main(String[] args) {
        test('h', 'e', 'l', 'l', 'o');
        test('m', 'a', 'i', 'n');
    }
    static void test(char... arg) {
        String s1 = new String(arg), s2 = s1.intern();
        System.out.println('"'+s1+'"'
            +(s1!=s2? " existed": " did not exist")+" in the pool before");
        System.out.println("is the same as \"hello\": "+(s2=="hello"));
        System.out.println("is the same as \"main\": "+(s2=="main"));
        System.out.println();
    }
}

测试首先创建一个池中不存在的新字符串实例。然后它调用intern()并比较参考文献。存在三种可能的情况:

  1. 如果池中存在相同内容的字符串,则将返回该字符串,该字符串必须是与不在池中的字符串不同的对象。

  2. 我们的字符串被添加到池中并返回。在这种情况下,两个参考是相同的。

  3. 将创建具有相同内容的新字符串并将其添加到池中。那么,返回的引用就会不同。

我们无法区分 1 和 3,因此如果 JVM 通常会向池中添加新字符串intern(),我们运气不好。但如果它添加了我们正在调用的实例intern()上,我们可以识别场景 2,并确定该字符串不在池中,而是作为测试的副作用添加的。

在我的机器上,它打印:

"hello" did not exist before
is the same as "hello": true
is the same as "main": false

"main" existed before
is the same as "hello": false
is the same as "main": true

Also on Ideone http://ideone.com/FrIAOX

表明"hello"进入时不存在test第一次方法,尽管有一个字符串文字"hello"稍后在代码中。所以这证明了字符串文字是被延迟解析的。由于我们已经添加了hellostring 手动,具有相同内容的字符串文字将解析为相同的实例。

相比之下,"main"字符串已经存在于池中,这很容易解释。 Java 启动器搜索main因此,作为副作用,要执行的方法会将该字符串添加到池中。

如果我们将测试的顺序交换为test('m', 'a', 'i', 'n'); test('h', 'e', 'l', 'l', 'o'); the "hello"字符串文字将在第一个中使用test调用并保留在池中,因此当我们在第二次调用中测试它时,该字符串将已经存在。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java HotSpot vm 中 String Literal 加载到 StringTable 的时间 的相关文章

随机推荐