openjdk17 从C++视角 看字节码ldc指令字符串加载过程
解析代码
-
常量池的解析 (
resolve_constant_at_impl
):- 这个函数主要负责根据常量池中的索引解析常量,并返回一个对象指针 (
oop
)。对于每种常量类型,代码根据常量池中的标记 (constantTag
) 执行不同的解析操作。 - 主要的常量类型包括:类 (
JVM_CONSTANT_Class
)、字符串 (JVM_CONSTANT_String
)、整数、浮动点数、动态常量 (JVM_CONSTANT_Dynamic
),以及方法引用等。
- 这个函数主要负责根据常量池中的索引解析常量,并返回一个对象指针 (
-
字符串常量解析 (
JVM_CONSTANT_String
):-
在
resolve_constant_at_impl
中,当遇到字符串常量 (JVM_CONSTANT_String
),会调用string_at_impl
方法。 -
string_at_impl
方法会检查字符串是否已经被 interned(即是否已经在常量池中存在)。如果字符串已经存在(str != NULL
),直接返回该字符串。如果字符串未找到,则通过StringTable::intern
方法将其放入字符串池中,随后更新常量池中的对应位置。
-
-
是否有新增字符串?
-
新增字符串:如果常量池中没有这个字符串(即该位置的常量为
NULL
),则会通过StringTable::intern
方法将该字符串 intern 到字符串池中。通过intern
,字符串会被添加到全局字符串池中,保证相同内容的字符串只有一份。 -
已经存在的字符串:如果字符串已经存在(即常量池位置不为
NULL
),则直接返回现有的字符串对象,并不会进行新增操作。
-
具体流程:
- 当常量池中的某个位置存储的是字符串常量时,
resolve_constant_at_impl
会调用string_at_impl
。 - 在
string_at_impl
中:- 首先检查该位置是否已经解析过字符串。如果已经解析过(即
resolved_references()->obj_at(obj_index)
不为NULL
),直接返回已存在的字符串对象。 - 如果尚未解析过:
- 使用
Symbol
(代表字符串的符号)从常量池中获取未解析的字符串。 - 调用
StringTable::intern
方法将字符串添加到字符串池中。 - 更新常量池中的该位置,指向新的字符串对象。
- 使用
- 首先检查该位置是否已经解析过字符串。如果已经解析过(即
因此,只有当常量池中的某个字符串常量未被解析过时,才会通过 StringTable::intern
进行新增,且这种新增操作仅在常量池中没有对应的字符串对象时发生。
结论:
是的,新增字符串常量只会在常量池中没有该字符串时发生。在常量池中的每个位置存储的是一个字符串常量的索引。如果该索引位置已经存在有效的字符串对象(即已经解析并 intern 过),则不会新增。而如果该位置没有对应的字符串对象,则会进行新增,并将其 intern 到字符串池中。
关键代码解释:
// 在 string_at_impl 方法中
oop str = this_cp->resolved_references()->obj_at(obj_index); // 获取常量池中的字符串对象
assert(str != Universe::the_null_sentinel(), "");
if (str != NULL) return str; // 如果已存在,直接返回
// 如果常量池中没有该字符串
Symbol* sym = this_cp->unresolved_string_at(which); // 获取常量池中的字符串符号
str = StringTable::intern(sym, CHECK_(NULL)); // 将字符串符号 intern 到全局字符串池中
this_cp->string_at_put(which, obj_index, str); // 更新常量池中的字符串对象
assert(java_lang_String::is_instance(str), "must be string"); // 确保结果是字符串
return str;
总结:
- 字符串常量会被 新增到字符串池中(intern),前提是常量池中尚未包含该字符串。
- 如果字符串已存在,则不会新增,而是直接返回已有的字符串对象。
##C++源码
##
JRT_ENTRY(void, InterpreterRuntime::resolve_ldc(JavaThread* current, Bytecodes::Code bytecode)) {
assert(bytecode == Bytecodes::_ldc ||
bytecode == Bytecodes::_ldc_w ||
bytecode == Bytecodes::_ldc2_w ||
bytecode == Bytecodes::_fast_aldc ||
bytecode == Bytecodes::_fast_aldc_w, "wrong bc");
ResourceMark rm(current);
const bool is_fast_aldc = (bytecode == Bytecodes::_fast_aldc ||
bytecode == Bytecodes::_fast_aldc_w);
LastFrameAccessor last_frame(current);
methodHandle m (current, last_frame.method());
Bytecode_loadconstant ldc(m, last_frame.bci());
// Double-check the size. (Condy can have any type.)
BasicType type = ldc.result_type();
switch (type2size[type]) {
case 2: guarantee(bytecode == Bytecodes::_ldc2_w, ""); break;
case 1: guarantee(bytecode != Bytecodes::_ldc2_w, ""); break;
default: ShouldNotReachHere();
}
// Resolve the constant. This does not do unboxing.
// But it does replace Universe::the_null_sentinel by null.
oop result = ldc.resolve_constant(CHECK);
assert(result != NULL || is_fast_aldc, "null result only valid for fast_aldc");
#ifdef ASSERT
{
// The bytecode wrappers aren't GC-safe so construct a new one
Bytecode_loadconstant ldc2(m, last_frame.bci());
int rindex = ldc2.cache_index();
if (rindex < 0)
rindex = m->constants()->cp_to_object_index(ldc2.pool_index());
if (rindex >= 0) {
oop coop = m->constants()->resolved_references()->obj_at(rindex);
oop roop = (result == NULL ? Universe::the_null_sentinel() : result);
assert(roop == coop, "expected result for assembly code");
}
}
#endif
current->set_vm_result(result);
if (!is_fast_aldc) {
// Tell the interpreter how to unbox the primitive.
guarantee(java_lang_boxing_object::is_instance(result, type), "");
int offset = java_lang_boxing_object::value_offset(type);
intptr_t flags = ((as_TosState(type) << ConstantPoolCacheEntry::tos_state_shift)
| (offset & ConstantPoolCacheEntry::field_index_mask));
current->set_vm_result_2((Metadata*)flags);
}
}
JRT_END
##
oop resolve_cached_constant_at(int cache_index, TRAPS) {
constantPoolHandle h_this(THREAD, this);
return resolve_constant_at_impl(h_this, _no_index_sentinel, cache_index, NULL, THREAD);
}
// Called to resolve constants in the constant pool and return an oop.
// Some constant pool entries cache their resolved oop. This is also
// called to create oops from constants to use in arguments for invokedynamic
oop ConstantPool::resolve_constant_at_impl(const constantPoolHandle& this_cp,
int index, int cache_index,
bool* status_return, TRAPS) {
oop result_oop = NULL;
Handle throw_exception;
if (cache_index == _possible_index_sentinel) {
// It is possible that this constant is one which is cached in the objects.
// We'll do a linear search. This should be OK because this usage is rare.
// FIXME: If bootstrap specifiers stress this code, consider putting in
// a reverse index. Binary search over a short array should do it.
assert(index > 0, "valid index");
cache_index = this_cp->cp_to_object_index(index);
}
assert(cache_index == _no_index_sentinel || cache_index >= 0, "");
assert(index == _no_index_sentinel || index >= 0, "");
if (cache_index >= 0) {
result_oop = this_cp->resolved_references()->obj_at(cache_index);
if (result_oop != NULL) {
if (result_oop == Universe::the_null_sentinel()) {
DEBUG_ONLY(int temp_index = (index >= 0 ? index : this_cp->object_to_cp_index(cache_index)));
assert(this_cp->tag_at(temp_index).is_dynamic_constant(), "only condy uses the null sentinel");
result_oop = NULL;
}
if (status_return != NULL) (*status_return) = true;
return result_oop;
// That was easy...
}
index = this_cp->object_to_cp_index(cache_index);
}
jvalue prim_value; // temp used only in a few cases below
constantTag tag = this_cp->tag_at(index);
if (status_return != NULL) {
// don't trigger resolution if the constant might need it
switch (tag.value()) {
case JVM_CONSTANT_Class:
{
CPKlassSlot kslot = this_cp->klass_slot_at(index);
int resolved_klass_index = kslot.resolved_klass_index();
if (this_cp->resolved_klasses()->at(resolved_klass_index) == NULL) {
(*status_return) = false;
return NULL;
}
// the klass is waiting in the CP; go get it
break;
}
case JVM_CONSTANT_String:
case JVM_CONSTANT_Integer:
case JVM_CONSTANT_Float:
case JVM_CONSTANT_Long:
case JVM_CONSTANT_Double:
// these guys trigger OOM at worst
break;
default:
(*status_return) = false;
return NULL;
}
// from now on there is either success or an OOME
(*status_return) = true;
}
switch (tag.value()) {
case JVM_CONSTANT_UnresolvedClass:
case JVM_CONSTANT_UnresolvedClassInError:
case JVM_CONSTANT_Class:
{
assert(cache_index == _no_index_sentinel, "should not have been set");
Klass* resolved = klass_at_impl(this_cp, index, CHECK_NULL);
// ldc wants the java mirror.
result_oop = resolved->java_mirror();
break;
}
case JVM_CONSTANT_Dynamic:
{
// Resolve the Dynamically-Computed constant to invoke the BSM in order to obtain the resulting oop.
BootstrapInfo bootstrap_specifier(this_cp, index);
// The initial step in resolving an unresolved symbolic reference to a
// dynamically-computed constant is to resolve the symbolic reference to a
// method handle which will be the bootstrap method for the dynamically-computed
// constant. If resolution of the java.lang.invoke.MethodHandle for the bootstrap
// method fails, then a MethodHandleInError is stored at the corresponding
// bootstrap method's CP index for the CONSTANT_MethodHandle_info. No need to
// set a DynamicConstantInError here since any subsequent use of this
// bootstrap method will encounter the resolution of MethodHandleInError.
// Both the first, (resolution of the BSM and its static arguments), and the second tasks,
// (invocation of the BSM), of JVMS Section 5.4.3.6 occur within invoke_bootstrap_method()
// for the bootstrap_specifier created above.
SystemDictionary::invoke_bootstrap_method(bootstrap_specifier, THREAD);
Exceptions::wrap_dynamic_exception(/* is_indy */ false, THREAD);
if (HAS_PENDING_EXCEPTION) {
// Resolution failure of the dynamically-computed constant, save_and_throw_exception
// will check for a LinkageError and store a DynamicConstantInError.
save_and_throw_exception(this_cp, index, tag, CHECK_NULL);
}
result_oop = bootstrap_specifier.resolved_value()();
BasicType type = Signature::basic_type(bootstrap_specifier.signature());
if (!is_reference_type(type)) {
// Make sure the primitive value is properly boxed.
// This is a JDK responsibility.
const char* fail = NULL;
if (result_oop == NULL) {
fail = "null result instead of box";
} else if (!is_java_primitive(type)) {
// FIXME: support value types via unboxing
fail = "can only handle references and primitives";
} else if (!java_lang_boxing_object::is_instance(result_oop, type)) {
fail = "primitive is not properly boxed";
}
if (fail != NULL) {
// Since this exception is not a LinkageError, throw exception
// but do not save a DynamicInError resolution result.
// See section 5.4.3 of the VM spec.
THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), fail);
}
}
LogTarget(Debug, methodhandles, condy) lt_condy;
if (lt_condy.is_enabled()) {
LogStream ls(lt_condy);
bootstrap_specifier.print_msg_on(&ls, "resolve_constant_at_impl");
}
break;
}
case JVM_CONSTANT_String:
assert(cache_index != _no_index_sentinel, "should have been set");
result_oop = string_at_impl(this_cp, index, cache_index, CHECK_NULL);
break;
case JVM_CONSTANT_DynamicInError:
case JVM_CONSTANT_MethodHandleInError:
case JVM_CONSTANT_MethodTypeInError:
{
throw_resolution_error(this_cp, index, CHECK_NULL);
break;
}
case JVM_CONSTANT_MethodHandle:
{
int ref_kind = this_cp->method_handle_ref_kind_at(index);
int callee_index = this_cp->method_handle_klass_index_at(index);
Symbol* name = this_cp->method_handle_name_ref_at(index);
Symbol* signature = this_cp->method_handle_signature_ref_at(index);
constantTag m_tag = this_cp->tag_at(this_cp->method_handle_index_at(index));
{ ResourceMark rm(THREAD);
log_debug(class, resolve)("resolve JVM_CONSTANT_MethodHandle:%d [%d/%d/%d] %s.%s",
ref_kind, index, this_cp->method_handle_index_at(index),
callee_index, name->as_C_string(), signature->as_C_string());
}
Klass* callee = klass_at_impl(this_cp, callee_index, CHECK_NULL);
// Check constant pool method consistency
if ((callee->is_interface() && m_tag.is_method()) ||
((!callee->is_interface() && m_tag.is_interface_method()))) {
ResourceMark rm(THREAD);
stringStream ss;
ss.print("Inconsistent constant pool data in classfile for class %s. "
"Method '", callee->name()->as_C_string());
signature->print_as_signature_external_return_type(&ss);
ss.print(" %s(", name->as_C_string());
signature->print_as_signature_external_parameters(&ss);
ss.print(")' at index %d is %s and should be %s",
index,
callee->is_interface() ? "CONSTANT_MethodRef" : "CONSTANT_InterfaceMethodRef",
callee->is_interface() ? "CONSTANT_InterfaceMethodRef" : "CONSTANT_MethodRef");
THROW_MSG_NULL(vmSymbols::java_lang_IncompatibleClassChangeError(), ss.as_string());
}
Klass* klass = this_cp->pool_holder();
Handle value = SystemDictionary::link_method_handle_constant(klass, ref_kind,
callee, name, signature,
THREAD);
result_oop = value();
if (HAS_PENDING_EXCEPTION) {
save_and_throw_exception(this_cp, index, tag, CHECK_NULL);
}
break;
}
case JVM_CONSTANT_MethodType:
{
Symbol* signature = this_cp->method_type_signature_at(index);
{ ResourceMark rm(THREAD);
log_debug(class, resolve)("resolve JVM_CONSTANT_MethodType [%d/%d] %s",
index, this_cp->method_type_index_at(index),
signature->as_C_string());
}
Klass* klass = this_cp->pool_holder();
Handle value = SystemDictionary::find_method_handle_type(signature, klass, THREAD);
result_oop = value();
if (HAS_PENDING_EXCEPTION) {
save_and_throw_exception(this_cp, index, tag, CHECK_NULL);
}
break;
}
case JVM_CONSTANT_Integer:
assert(cache_index == _no_index_sentinel, "should not have been set");
prim_value.i = this_cp->int_at(index);
result_oop = java_lang_boxing_object::create(T_INT, &prim_value, CHECK_NULL);
break;
case JVM_CONSTANT_Float:
assert(cache_index == _no_index_sentinel, "should not have been set");
prim_value.f = this_cp->float_at(index);
result_oop = java_lang_boxing_object::create(T_FLOAT, &prim_value, CHECK_NULL);
break;
case JVM_CONSTANT_Long:
assert(cache_index == _no_index_sentinel, "should not have been set");
prim_value.j = this_cp->long_at(index);
result_oop = java_lang_boxing_object::create(T_LONG, &prim_value, CHECK_NULL);
break;
case JVM_CONSTANT_Double:
assert(cache_index == _no_index_sentinel, "should not have been set");
prim_value.d = this_cp->double_at(index);
result_oop = java_lang_boxing_object::create(T_DOUBLE, &prim_value, CHECK_NULL);
break;
default:
DEBUG_ONLY( tty->print_cr("*** %p: tag at CP[%d/%d] = %d",
this_cp(), index, cache_index, tag.value()));
assert(false, "unexpected constant tag");
break;
}
if (cache_index >= 0) {
// Benign race condition: resolved_references may already be filled in.
// The important thing here is that all threads pick up the same result.
// It doesn't matter which racing thread wins, as long as only one
// result is used by all threads, and all future queries.
oop new_result = (result_oop == NULL ? Universe::the_null_sentinel() : result_oop);
oop old_result = this_cp->resolved_references()
->atomic_compare_exchange_oop(cache_index, new_result, NULL);
if (old_result == NULL) {
return result_oop; // was installed
} else {
// Return the winning thread's result. This can be different than
// the result here for MethodHandles.
if (old_result == Universe::the_null_sentinel())
old_result = NULL;
return old_result;
}
} else {
assert(result_oop != Universe::the_null_sentinel(), "");
return result_oop;
}
}
##
oop ConstantPool::string_at_impl(const constantPoolHandle& this_cp, int which, int obj_index, TRAPS) {
// If the string has already been interned, this entry will be non-null
oop str = this_cp->resolved_references()->obj_at(obj_index);
assert(str != Universe::the_null_sentinel(), "");
if (str != NULL) return str;
Symbol* sym = this_cp->unresolved_string_at(which);
str = StringTable::intern(sym, CHECK_(NULL));
this_cp->string_at_put(which, obj_index, str);
assert(java_lang_String::is_instance(str), "must be string");
return str;
}
##gdb调试堆栈
#0 typeArrayOopDesc::object_size (lh=-1072691200, length=3) at /home/yym/openjdk17/jdk17-master/src/hotspot/share/oops/typeArrayOop.hpp:120
#1 0x00007ffff6b8bf1d in TypeArrayKlass::allocate_common (this=0x100040820, length=3, do_zero=true, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/oops/typeArrayKlass.cpp:92
#2 0x00007ffff60b06c8 in TypeArrayKlass::allocate (this=0x100040820, length=3, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/oops/typeArrayKlass.hpp:68
#3 0x00007ffff685be50 in oopFactory::new_byteArray (length=3, __the_thread__=0x7ffff0028920) at /home/yym/openjdk17/jdk17-master/src/hotspot/share/memory/oopFactory.cpp:60
#4 0x00007ffff6379b92 in java_lang_String::basic_create (length=3, is_latin1=true, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/classfile/javaClasses.cpp:285
#5 0x00007ffff6379c77 in java_lang_String::create_from_unicode (unicode=0x7fff7007d0e0, length=3, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/classfile/javaClasses.cpp:297
#6 0x00007ffff6a8da0d in StringTable::do_intern (string_or_null_h=..., name=0x7fff7007d0e0, len=3, hash=113046, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/classfile/stringTable.cpp:349
#7 0x00007ffff6a8d96e in StringTable::intern (string_or_null_h=..., name=0x7fff7007d0e0, len=3, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/classfile/stringTable.cpp:338
#8 0x00007ffff6a8d6a0 in StringTable::intern (symbol=0x7ffff0529fd0, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/classfile/stringTable.cpp:298
#9 0x00007ffff606fca4 in ConstantPool::string_at_impl (this_cp=..., which=77, obj_index=12, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/oops/constantPool.cpp:1227
#10 0x00007ffff606ed90 in ConstantPool::resolve_constant_at_impl (this_cp=..., index=77, cache_index=12, status_return=0x0, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/oops/constantPool.cpp:1051
#11 0x00007ffff5df5f7d in ConstantPool::resolve_cached_constant_at (this=0x7fffd9c0f108, cache_index=12, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/oops/constantPool.hpp:743
#12 0x00007ffff5df58a8 in Bytecode_loadconstant::resolve_constant (this=0x7ffff7bfe6a0, __the_thread__=0x7ffff0028920)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/interpreter/bytecode.cpp:215
#13 0x00007ffff6368055 in InterpreterRuntime::resolve_ldc (current=0x7ffff0028920, bytecode=Bytecodes::_fast_aldc)
at /home/yym/openjdk17/jdk17-master/src/hotspot/share/interpreter/interpreterRuntime.cpp:186
#14 0x00007fffe10279b7 in ?? ()
#15 0x00007fffe102792b in ?? ()
#16 0x00000000ffa1a0d0 in ?? ()
#17 0x00007ffff7bfe7a0 in ?? ()
#18 0x00007fffd9c0fae3 in ?? ()
#19 0x00007ffff7bfe808 in ?? ()
#20 0x00007fffd9c0e800 in ?? ()
#21 0x0000000000000000 in ?? ()