字符串去重是G1引入的新特性,在我們?nèi)粘i_發(fā)中,字符串基本上是使用最多的類型。而String對象是不可變的,通常會消耗大量的內(nèi)存,這里面有一部分是冗余的。String對象有自己的專屬char[],而這個char沒有暴露給客戶端。那么jvm就可以通過判斷兩個字符串是否一致,從而將一個字符串的char[]共享為另一個字符串的char[],從而達到節(jié)省內(nèi)存的目的。
字符串去重步驟
- 找到能去重的對象
字符串是否可以去重需要滿足2個條件:1. 字符串位于新生代,若是復(fù)制到s區(qū),則年齡>StringDeduplicationAgeThreshold(默認為3),才可以去重;若是字符串晉升到old區(qū),且非大對象且年齡<StringDeduplicationAgeThreshold,則可以去重;2. fullgc只考慮第二個條件
bool G1StringDedup::is_candidate_from_mark(oop obj) {
if (java_lang_String::is_instance(obj)) {
bool from_young = G1CollectedHeap::heap()->heap_region_containing_raw(obj)->is_young();
if (from_young && obj->age() < StringDeduplicationAgeThreshold) {
//young to old且年齡<StringDeduplicationAgeThreshold
return true;
}
}
return false;
}
void G1StringDedup::enqueue_from_mark(oop java_string) {
assert(is_enabled(), "String deduplication not enabled");
if (is_candidate_from_mark(java_string)) {
G1StringDedupQueue::push(0 /* worker_id */, java_string);
}
}
bool G1StringDedup::is_candidate_from_evacuation(bool from_young, bool to_young, oop obj) {
if (from_young && java_lang_String::is_instance(obj)) {
if (to_young && obj->age() == StringDeduplicationAgeThreshold) {
//young to young,即到s區(qū),且年齡>StringDeduplicationAgeThreshold
return true;
}
if (!to_young && obj->age() < StringDeduplicationAgeThreshold) {
//young to old且年齡<StringDeduplicationAgeThreshold
return true;
}
}
// Not a candidate
return false;
}
- 去重
由單獨的去重線程完成。開始去重時,會先查找字符數(shù)組是否存在,若存在則調(diào)整指針,共享字符串?dāng)?shù)組,釋放多余的字符串?dāng)?shù)組。
去重在G1CollectHeap初始化中啟動,由hashtable存儲字符串?dāng)?shù)組的值,在字符串回收時可以對hashtable進行遍歷。 - 回收
在ygc、并發(fā)標記、fullgc中都有可能進行字符串去重的回收。
參數(shù)
字符串去重的參數(shù)有UseStringDeduplication和StringDeduplicationAgeThreshold,前者代表是否開啟去重功能,后者如上述。
雖然字符串去重能明顯減少內(nèi)存,但增加了gc處理時間,使用時需驗證效果。
字符串去重和String.intern方法
String.intern方法:通過StringTable(HashTable實現(xiàn))存儲字符串對象,如果StringTable已存在該對象,則返回該對象在常量池中的引用。否則,在StringTable加入該對象,然后返回引用。
兩者區(qū)別有2點:
- intern緩存的是字符串對象,而字符串去重是字符串里的字符數(shù)組。這里intern這么搞主要是gc并發(fā)標記用。試想如果intern使用字符數(shù)組的話,那么當(dāng)Str1死亡還未回收時,另一個Str2執(zhí)行intern則發(fā)現(xiàn)在Stringtable中沒有,又會加入到Stringtable中,而此時只需激活Str1并返回引用即可。這里假設(shè)Str1和Str2是equals。
- intern需要顯式調(diào)用,字符串去重是jvm自己處理。