簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!
看得越多,懂的越少,還年輕,多學習!
接著上文《JVM源碼分析之堆內(nèi)存的初始化》,本文對新生代的實現(xiàn)進行分析,在JVM內(nèi)部提供了多種方式來實現(xiàn)新生代的內(nèi)存,如DefNewGeneration
、ParNewGeneration
和ASParNewGeneration
等,由虛擬機的啟動參數(shù)決定最終采用哪種方式進行實現(xiàn)。
DefNewGeneration
DefNewGeneration
是一個young generation
,包含了eden、from and to
內(nèi)存區(qū),當虛擬機啟動參數(shù)中沒有指定垃圾回收算法時,默認使用該方式實現(xiàn)新生代,定義如下:
1、_next_gen
指向下一個內(nèi)存代;
2、_tenuring_threshold
為對象的晉升閥值,如果某個對象經(jīng)歷過_tenuring_threshold
次gc后依然存活,則可以晉升到老年代;
內(nèi)存初始化實現(xiàn)
實現(xiàn)位于openjdk\hotspot\src\share\vm\memory\defNewGeneration.cpp
分別通過EdenSpace
和ContiguousSpace
類實現(xiàn)新生代的eden
和from to
區(qū)域,(其中Contiguous是"連續(xù)"的意思,表示一塊連續(xù)的內(nèi)存空間),其中EdenSpace
在實現(xiàn)上是繼承自ContiguousSpace
的。
1、如果_eden_space、_from_space、_to_space
其中任何一個為空,說明新生代分配內(nèi)存失敗,則虛擬機退出;
2、根據(jù)SurvivorRatio
計算from to
內(nèi)存區(qū)應該分配的大小;
3、剩余的空間大小分配給eden
內(nèi)存區(qū);
GC過程實現(xiàn)
基本思路:
1、掃描內(nèi)存堆所有的根對象集T,并把它們復制到新的內(nèi)存塊,一般為新生代的to
內(nèi)存區(qū)或老年代;
2、分析掃描這些根對象集T的所有引用對象集T1,并把它們復制到新的內(nèi)存塊;
3、繼續(xù)分析對象集T1所引用的對象集T2,一直迭代下去,直到對象集Tn為空;
GC過程位于collect()
方法中:
GC檢測
1、確保當前是一次FGC
,或需要分配的內(nèi)存大小size
大于0,否則不需要執(zhí)行一次gc操作;
2、因為當前是最年輕代的管理器,確保有下一個內(nèi)存管理器;
3、通過collection_attempt_is_safe()
方法判斷當前的GC是否安全,實現(xiàn)如下:
安全的GC必須滿足如下條件:
1、survivor
中的to
區(qū)為空;
2、下一個內(nèi)存代有足夠的內(nèi)存容納新生代的所有對象;
否則,設(shè)置_incremental_collection_failed
為false
,即當前minor gc
不可用,通知內(nèi)存堆管理器不要再嘗試增量式GC了,因為肯定會失敗;
GC開始
GC準備工作:
1、初始化IsAliveClosure
和ScanWeakRefClosure
;
2、清空age_table
數(shù)據(jù)和to
區(qū);
3、初始化FastScanClosure,負責存活對象的標識和復制;
根對象的標識復制
我們都知道和根對象有聯(lián)系的對象都是活躍對象,那么如何快速確定內(nèi)存代中所有的活躍對象呢?
1、將內(nèi)存代的跟對象和被其它內(nèi)存代對象引用的對象復制到to
區(qū)域,這些對象作為活躍對象,雖然其它內(nèi)存代的對象可能在下次Full GC成為垃圾對象,但目前的Minor GC不能將這些對象當做垃圾對象進行處理;
2、遞歸遍歷這些活躍對象,將其所引用的在該內(nèi)存代的對象復制到To區(qū)域,最終剩下的對象就是垃圾對象了。
其中gen_process_strong_roots()
負責查找當前代中的根對象和被其它內(nèi)存代對象引用的對象,并復制到to
區(qū)域,實現(xiàn)如下:
if (!do_code_roots) {
SharedHeap::process_strong_roots(activate_scope, collecting_perm_gen, so,
not_older_gens, NULL, older_gens);
} else {
bool do_code_marking = (activate_scope || nmethod::oops_do_marking_is_active());
CodeBlobToOopClosure code_roots(not_older_gens, /*do_marking=*/ do_code_marking);
SharedHeap::process_strong_roots(activate_scope, collecting_perm_gen, so,
not_older_gens, &code_roots, older_gens);
}
if (younger_gens_as_roots) {
if (!_gen_process_strong_tasks->is_task_claimed(GCH_PS_younger_gens)) {
for (int i = 0; i < level; i++) {
not_older_gens->set_generation(_gens[i]);
_gens[i]->oop_iterate(not_older_gens);
}
not_older_gens->reset_generation();
}
}
// When collection is parallel, all threads get to cooperate to do
// older-gen scanning.
for (int i = level+1; i < _n_gens; i++) {
older_gens->set_generation(_gens[i]);
rem_set()->younger_refs_iterate(_gens[i], older_gens);
older_gens->reset_generation();
}
_gen_process_strong_tasks->all_tasks_completed();
假設(shè)當前內(nèi)存堆上有如下對象模型(圖片出自http://www.importnew.com/21063.html ),其中深色對象為根對象,箭頭代表對象的引用關(guān)系。
根對象的查找過程如下:
1、調(diào)用SharedHeap::process_strong_roots()
方法遍歷當前內(nèi)存代中所有根對象,eden
和from
區(qū)的根對象將被復制到to
區(qū),被復制的對象C1使用橙色表示;
2、遍歷更低內(nèi)存代和更高內(nèi)存代對象,如果這些對象有引用當前內(nèi)存代的對象,如C2和C3分別被高低內(nèi)存代中L1和H2對象所引用,則把對象C2和C3復制到to
區(qū);
存活對象的遞歸標記
FastEvacuateFollowersClosure.do_void()
方法實現(xiàn)活躍對象的遞歸標記,通過廣度優(yōu)先搜索算法遍歷掃描活躍對象,算法實現(xiàn)如下:
當各分代的空閑分配指針不再變化時,說明所有可觸及對象都遞歸標記完成,否則調(diào)用oop_since_save_marks_iterate()
進行遍歷標記。
1、循環(huán)條件no_allocs_since_save_marks()
實現(xiàn)如下:
bool GenCollectedHeap::no_allocs_since_save_marks(int level) {
for (int i = level; i < _n_gens; i++) {
if (!_gens[i]->no_allocs_since_save_marks()) return false;
}
return perm_gen()->no_allocs_since_save_marks();
}
主要檢查當前代、更高代以及永久代scanned指針_saved_mark_word是否與當前空閑分配指針位置相同,如DefNewGeneration
的實現(xiàn)如下:
bool DefNewGeneration::no_allocs_since_save_marks() {
assert(eden()->saved_mark_at_top(), "Violated spec - alloc in eden");
assert(from()->saved_mark_at_top(), "Violated spec - alloc in from");
return to()->saved_mark_at_top();
}
bool saved_mark_at_top() const { return saved_mark_word() == top(); }
2、循環(huán)處理oop_since_save_marks_iterate()
實現(xiàn)如下:
void GenCollectedHeap::
oop_since_save_marks_iterate(int level,
OopClosureType* cur,
OopClosureType* older) {
_gens[level]->oop_since_save_marks_iterate##nv_suffix(cur);
for (int i = level+1; i < n_gens(); i++) {
_gens[i]->oop_since_save_marks_iterate##nv_suffix(older);
}
perm_gen()->oop_since_save_marks_iterate##nv_suffix(older);
}
主要對當前代、更高代以及永久代的對象進行遍歷處理,不過為什么要對更高代的對象進行遍歷呢?主要為了防止在復制過程中,有些對象可能直接晉升到更高代內(nèi)存中。
其中DefNewGeneration
中的實現(xiàn)如下:
void DefNewGeneration::
oop_since_save_marks_iterate##nv_suffix(OopClosureType* cl) {
cl->set_generation(this);
eden()->oop_since_save_marks_iterate##nv_suffix(cl);
to()->oop_since_save_marks_iterate##nv_suffix(cl);
from()->oop_since_save_marks_iterate##nv_suffix(cl);
cl->reset_generation();
save_marks();
}
主要調(diào)用新生代各個區(qū)的同名方法進行處理,實現(xiàn)如下:
void ContiguousSpace::
oop_since_save_marks_iterate##nv_suffix(OopClosureType* blk) {
HeapWord* t;
HeapWord* p = saved_mark_word();
assert(p != NULL, "expected saved mark");
const intx interval = PrefetchScanIntervalInBytes;
do {
t = top();
while (p < t) {
Prefetch::write(p, interval);
debug_only(HeapWord* prev = p);
oop m = oop(p);
p += m->oop_iterate(blk);
}
} while (t < top());
set_saved_mark_word(p);
}
因為在scanned指針到空閑分配指針之間的區(qū)域是已分配但未掃描的對象,對這塊區(qū)域的對象調(diào)用遍歷函數(shù)進行處理,標記所引用的對象,并保存新的scanned指針。
圖解過程
1、遞歸標記的開始時,Scanned指針為To區(qū)域的起點,Top指針指向to
區(qū)的空閑位置,Scanned到Top之間的對象就是需要進行遞歸處理的對象;
2、第一輪遞歸標記后,根集對象中C3引用了C5,C5被移動至To區(qū)域,Scanned指針指向已處理完的對象,這時C1、C2、C3均已被遍歷完畢,現(xiàn)在C5需要被遍歷,其中綠色對象代表被移動到To區(qū)域的非根集對象;
3、第二輪遞歸標記后,C5引用了C7、C8,這兩個對象被移動到了To區(qū)域,這時C5已被遍歷完畢,現(xiàn)在C7、C8需要被遍歷;
4、第三輪標記沒有任何引用被發(fā)現(xiàn),Scanned指針追上了Top指針,所有存活對象被遍歷完畢;
5、假如還有一個C12為C8所引用,但是To區(qū)域沒有足夠的空間,那么C12就會晉升到更高的內(nèi)存代(老年代)
對象的標記和復制實現(xiàn)
對象的標記和復制過程最終由FastScanClosure
的do_oop
方法實現(xiàn),其中do_oop
方法又調(diào)用了do_oop_work
方法,do_oop_work
究竟做了什么?
使用模板函數(shù)解決不同類型的指針(實際oop和壓縮過的narrowOop):
1、當該指針對象非空時,通過decode_heap_oop_not_null
方法獲取對象obj
;
2、如果該對象obj
在遍歷區(qū)域(_boudary是在FastScanClosure
初始化的時候,為初始化時指定代的結(jié)束地址,與當前遍歷代的起始地址_gen_boundary
共同作為對象的訪問邊界),則通過obj->is_forwarded()
判斷該對象是否已經(jīng)標記過,如果對象沒有被標識過,即其標記狀態(tài)不為marked_value,則通過_g->copy_to_survivor_space(obj)
方法把該對象復制到to
區(qū)域;
3、根據(jù)是否使用指針壓縮將新的對象地址進行壓縮;
其中copy_to_survivor_space()
方法中對象的復制過程實現(xiàn)如下:
1、如果該對象的age
小于_tenuring_threshold
(直接晉升到老年代的閾值),則將其分配到to
區(qū)域,分配成功后,將原對象的數(shù)據(jù)內(nèi)容復制到to區(qū)域新分配的對象上,并增加該對象的復制計數(shù)age和更新ageTable;
2、否則通過_next_gen->promote()
嘗試將該對象晉升,如果晉升失敗,則調(diào)用handle_promotion_failure()
處理失敗的對象;
3、最后調(diào)用forward_to()
設(shè)置原對象的對象頭為轉(zhuǎn)發(fā)指針,表示該對象已被復制,并指明該對象已經(jīng)被復制到什么位置;
處理晉升成功
如果GC過程中沒有發(fā)生對象的晉升失敗,則執(zhí)行如下邏輯:
1、既然所有對象都晉升成功了,說明存活對象都轉(zhuǎn)移到了to
區(qū)域或老年代,則通過clear
方法清空eden
和from
區(qū);
2、通過swap_spaces
方法交換from
和to
區(qū)域,為下次GC作準備,swap_spaces
實現(xiàn)如下:
通過交換_from_space
和_to_space
的起始地址實現(xiàn)from
和to
區(qū)的角色互換,并重新設(shè)置eden
的_next_compaction_space
,即eden
的下一個內(nèi)存區(qū)域;
3、from
和to
區(qū)互換之后,當前的to
區(qū)應該已經(jīng)是塊空區(qū)域了;
4、調(diào)用ageTable
的compute_tenuring_threshold
方法對晉升閥值_tenuring_threshold
重新設(shè)置,實現(xiàn)如下:
其中survivor_capacity
是to
區(qū)的容量,假設(shè)為1G,TargetSurvivorRatio
默認為50,計算邏輯大概如下:
其中desired_survivor_size
默認為survivor_capacity
的一半,age_table
記錄了各個年齡段的對象總大小,按年齡從小到大,累加對象大小,當總大小超過survivor_capacity
時,比較當前的age
和MaxTenuringThreshold
的大小,并返回較小者,其中MaxTenuringThreshold
默認為15;
處理晉升失敗
如果GC過程中存在晉升失敗,則執(zhí)行如下邏輯:
1、當對象被標記為活躍對象時,其對象頭markword
指向經(jīng)過復制后對象的新地址,remove_forwarding_pointers
負責恢復晉升失敗對象的markOop
,實現(xiàn)如下:
當對象晉升失敗時,對象的oop
會被保存在_objs_with_preserved_marks
棧中,對應的對象頭markOop
被保存在_preserved_marks_of_objs
棧中,通過這兩個棧,可以對晉升失敗的對象的對象頭進行恢復;
2、對from
和to
區(qū)進行互換,并設(shè)置from
的下一片需要進行壓縮的區(qū)域為to
區(qū),因為當有對象晉升失敗時,并不會清空eden
和from
區(qū),這時對from
和to
區(qū)互換,但to
區(qū)還有活躍對象,這樣在隨后觸發(fā)的FGC
能夠?qū)?code>from和to
進行壓縮處理;
3、設(shè)置新生代的minor gc
失敗標識,并通知下一個內(nèi)存代(老年代)發(fā)生晉升失敗,比如在ConcurrentMarkSweepGeneration
會根據(jù)參數(shù)CMSDumpAtPromotionFailure
進行dump輸出以供JVM問題診斷,實現(xiàn)如下:
結(jié)尾
1、設(shè)置from
和to
區(qū)域的并發(fā)遍歷指針時的安全值為碰撞指針所在位置;
2、更新堆的最后一次gc的時間;
我是占小狼,如果讀完覺得有收獲的話,歡迎點贊加關(guān)注