##String native方法
public native String intern();
1. intern()
方法在 Java 中的作用
在 Java 中,String#intern()
方法是一个本地方法,用于确保字符串常量池中只有一个字符串的实例。当你调用 intern()
方法时,它会检查字符串常量池中是否已经存在一个相同的字符串。如果存在,它返回池中的字符串引用,否则将字符串添加到常量池中并返回该引用。
2. JVM_InternString
C++ 实现
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str)) JvmtiVMObjectAllocEventCollector oam; if (str == NULL) return NULL; oop string = JNIHandles::resolve_non_null(str); oop result = StringTable::intern(string, CHECK_NULL); return (jstring) JNIHandles::make_local(THREAD, result); JVM_END
这个 C++ 函数是 String#intern()
的本地实现。它首先获取 jstring
类型的字符串引用,然后通过 StringTable::intern
来查找或插入字符串到字符串常量池中。如果字符串已经存在,则返回池中的字符串引用,否则将其插入池中。
3. StringTable::intern
方法
oop StringTable::intern(oop string, TRAPS) { if (string == NULL) return NULL; ResourceMark rm(THREAD); int length; Handle h_string (THREAD, string); jchar* chars = java_lang_String::as_unicode_string(string, length, CHECK_NULL); oop result = intern(h_string, chars, length, CHECK_NULL); return result; }
- 这里的
intern
方法接受一个oop
(对象指针)类型的字符串,并将其转换为 Unicode 字符数组。然后它调用另一个重载的intern
方法,实际执行字符串查找或插入操作。 ResourceMark
是为了确保在执行期间分配的资源能够在结束时被回收。
4. 字符串查找和插入 (do_lookup
和 do_intern
)
在 StringTable::do_lookup
中,我们执行的是对字符串常量池的查找操作。它首先会尝试在本地表中查找该字符串。如果找到了,它就返回找到的字符串对象(found_string
)。
oop StringTable::do_lookup(const jchar* name, int len, uintx hash) { Thread* thread = Thread::current(); StringTableLookupJchar lookup(thread, hash, name, len); StringTableGet stg(thread); bool rehash_warning; _local_table->get(thread, lookup, stg, &rehash_warning); update_needs_rehash(rehash_warning); return stg.get_res_oop(); }
- 在本地表(
_local_table
)中查找字符串时,它会计算字符串的哈希值并查找对应的条目。 - 如果查找到了字符串,
stg.get_res_oop()
会返回字符串对象(oop
)。
5. 字符串插入操作 (do_intern
)
如果在查找时未找到该字符串,do_intern
会创建一个新的字符串对象并将其插入到常量池中。
oop StringTable::do_intern(Handle string_or_null_h, const jchar* name, int len, uintx hash, TRAPS) { HandleMark hm(THREAD); // cleanup strings created Handle string_h; if (!string_or_null_h.is_null()) { string_h = string_or_null_h; } else { string_h = java_lang_String::create_from_unicode(name, len, CHECK_NULL); } assert(java_lang_String::equals(string_h(), name, len), "string must be properly initialized"); assert(len == java_lang_String::length(string_h()), "Must be same length"); // Check for deduplication if (StringDedup::is_enabled()) { StringDedup::notify_intern(string_h()); } StringTableLookupOop lookup(THREAD, hash, string_h); StringTableGet stg(THREAD); bool rehash_warning; do { WeakHandle wh(_oop_storage, string_h); if (_local_table->insert(THREAD, lookup, wh, &rehash_warning)) { update_needs_rehash(rehash_warning); return wh.resolve(); } if (_local_table->get(THREAD, lookup, stg, &rehash_warning)) { update_needs_rehash(rehash_warning); return stg.get_res_oop(); } } while (true); }
- 创建字符串对象:如果传入的字符串为空,它会通过
create_from_unicode
创建一个新的字符串对象。 - 插入字符串:然后通过
WeakHandle
将字符串插入到本地表中。 - 检查重复:如果存在重复的字符串,它会通过循环保证字符串只有一个实例。如果另一个线程已经插入了相同的字符串,当前线程会发现并直接返回该字符串。
6. 字符串表的初始化 (create_table
)
在 JVM 启动时,字符串常量池会通过 StringTable::create_table
方法来初始化本地表。
void StringTable::create_table() { size_t start_size_log_2 = ceil_log2(StringTableSize); _current_size = ((size_t)1) << start_size_log_2; log_trace(stringtable)("Start size: " SIZE_FORMAT " (" SIZE_FORMAT ")", _current_size, start_size_log_2); _local_table = new StringTableHash(start_size_log_2, END_SIZE, REHASH_LEN); _oop_storage = OopStorageSet::create_weak("StringTable Weak", mtSymbol); _oop_storage->register_num_dead_callback(&gc_notification); }
StringTableSize
:这是常量池的初始大小,它会根据配置进行调整。StringTableHash
:用于实现字符串常量池的哈希表。OopStorageSet
:用于存储字符串对象,WeakHandle
确保对象在垃圾回收时不会被误删除。
总结
C++源码中清晰地展示了如何通过 StringTable
管理字符串常量池。intern()
方法的核心逻辑包括查找、插入和确保字符串唯一性的操作。不同的函数(如 do_lookup
, do_intern
)和结构(如 WeakHandle
, StringTableHash
)确保了线程安全、性能和内存管理。
##C++源码
##字符串表查找字符串
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))JvmtiVMObjectAllocEventCollector oam;if (str == NULL) return NULL;oop string = JNIHandles::resolve_non_null(str);oop result = StringTable::intern(string, CHECK_NULL);return (jstring) JNIHandles::make_local(THREAD, result);
JVM_END
##
oop StringTable::intern(oop string, TRAPS) {if (string == NULL) return NULL;ResourceMark rm(THREAD);int length;Handle h_string (THREAD, string);jchar* chars = java_lang_String::as_unicode_string(string, length,CHECK_NULL);oop result = intern(h_string, chars, length, CHECK_NULL);return result;
}
##
oop StringTable::intern(Handle string_or_null_h, const jchar* name, int len, TRAPS) {// shared table always uses java_lang_String::hash_codeunsigned int hash = java_lang_String::hash_code(name, len);oop found_string = lookup_shared(name, len, hash);if (found_string != NULL) {return found_string;}if (_alt_hash) {hash = hash_string(name, len, true);}found_string = do_lookup(name, len, hash);if (found_string != NULL) {return found_string;}return do_intern(string_or_null_h, name, len, hash, THREAD);
}
##从本地表查找
oop StringTable::do_lookup(const jchar* name, int len, uintx hash) {Thread* thread = Thread::current();StringTableLookupJchar lookup(thread, hash, name, len);StringTableGet stg(thread);bool rehash_warning;_local_table->get(thread, lookup, stg, &rehash_warning);update_needs_rehash(rehash_warning);return stg.get_res_oop();
}
##在本地表没有找到,则创建一个新的插入到本地表
oop StringTable::do_intern(Handle string_or_null_h, const jchar* name,int len, uintx hash, TRAPS) {HandleMark hm(THREAD); // cleanup strings createdHandle string_h;if (!string_or_null_h.is_null()) {string_h = string_or_null_h;} else {string_h = java_lang_String::create_from_unicode(name, len, CHECK_NULL);}assert(java_lang_String::equals(string_h(), name, len),"string must be properly initialized");assert(len == java_lang_String::length(string_h()), "Must be same length");// Notify deduplication support that the string is being interned. A string// must never be deduplicated after it has been interned. Doing so interferes// with compiler optimizations done on e.g. interned string literals.if (StringDedup::is_enabled()) {StringDedup::notify_intern(string_h());}StringTableLookupOop lookup(THREAD, hash, string_h);StringTableGet stg(THREAD);bool rehash_warning;do {// Callers have already looked up the String using the jchar* name, so just go to add.WeakHandle wh(_oop_storage, string_h);// The hash table takes ownership of the WeakHandle, even if it's not inserted.if (_local_table->insert(THREAD, lookup, wh, &rehash_warning)) {update_needs_rehash(rehash_warning);return wh.resolve();}// In case another thread did a concurrent add, return value already in the table.// This could fail if the String got gc'ed concurrently, so loop back until success.if (_local_table->get(THREAD, lookup, stg, &rehash_warning)) {update_needs_rehash(rehash_warning);return stg.get_res_oop();}} while(true);
}
##jvm在初始化的时候,调用create_table生成本地表。
if (UseSharedSpaces) {// Read the data structures supporting the shared spaces (shared// system dictionary, symbol table, etc.). After that, access to// the file (other than the mapped regions) is no longer needed, and// the file is closed. Closing the file does not affect the// currently mapped regions.MetaspaceShared::initialize_shared_spaces();StringTable::create_table();} else
#endif{SymbolTable::create_table();StringTable::create_table();}
void StringTable::create_table() {size_t start_size_log_2 = ceil_log2(StringTableSize);_current_size = ((size_t)1) << start_size_log_2;log_trace(stringtable)("Start size: " SIZE_FORMAT " (" SIZE_FORMAT ")",_current_size, start_size_log_2);_local_table = new StringTableHash(start_size_log_2, END_SIZE, REHASH_LEN);_oop_storage = OopStorageSet::create_weak("StringTable Weak", mtSymbol);_oop_storage->register_num_dead_callback(&gc_notification);
}