JVM源碼分析之新生代DefNewGeneration的實現(xiàn)

簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!

看得越多,懂的越少,還年輕,多學習!

接著上文《JVM源碼分析之堆內(nèi)存的初始化》,本文對新生代的實現(xiàn)進行分析,在JVM內(nèi)部提供了多種方式來實現(xiàn)新生代的內(nèi)存,如DefNewGenerationParNewGenerationASParNewGeneration等,由虛擬機的啟動參數(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

分別通過EdenSpaceContiguousSpace類實現(xiàn)新生代的edenfrom 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_failedfalse,即當前minor gc不可用,通知內(nèi)存堆管理器不要再嘗試增量式GC了,因為肯定會失敗;

GC開始

GC準備工作:
1、初始化IsAliveClosureScanWeakRefClosure
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)存代中所有根對象,edenfrom區(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指針,所有存活對象被遍歷完畢;

Paste_Image.png

5、假如還有一個C12為C8所引用,但是To區(qū)域沒有足夠的空間,那么C12就會晉升到更高的內(nèi)存代(老年代)


對象的標記和復制實現(xiàn)

對象的標記和復制過程最終由FastScanClosuredo_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方法清空edenfrom區(qū);
2、通過swap_spaces方法交換fromto區(qū)域,為下次GC作準備,swap_spaces實現(xiàn)如下:

通過交換_from_space_to_space的起始地址實現(xiàn)fromto區(qū)的角色互換,并重新設(shè)置eden_next_compaction_space,即eden的下一個內(nèi)存區(qū)域;

3、fromto區(qū)互換之后,當前的to區(qū)應該已經(jīng)是塊空區(qū)域了;
4、調(diào)用ageTablecompute_tenuring_threshold方法對晉升閥值_tenuring_threshold重新設(shè)置,實現(xiàn)如下:

其中survivor_capacityto區(qū)的容量,假設(shè)為1G,TargetSurvivorRatio默認為50,計算邏輯大概如下:
其中desired_survivor_size默認為survivor_capacity的一半,age_table記錄了各個年齡段的對象總大小,按年齡從小到大,累加對象大小,當總大小超過survivor_capacity時,比較當前的ageMaxTenuringThreshold的大小,并返回較小者,其中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、對fromto區(qū)進行互換,并設(shè)置from的下一片需要進行壓縮的區(qū)域為to區(qū),因為當有對象晉升失敗時,并不會清空edenfrom區(qū),這時對fromto區(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è)置fromto區(qū)域的并發(fā)遍歷指針時的安全值為碰撞指針所在位置;
2、更新堆的最后一次gc的時間;


我是占小狼,如果讀完覺得有收獲的話,歡迎點贊加關(guān)注


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • JVM架構(gòu) 當一個程序啟動之前,它的class會被類裝載器裝入方法區(qū)(Permanent區(qū)),執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,693評論 0 7
  • 原文閱讀 前言 這段時間懈怠了,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點贊,這博客我...
    碼農(nóng)戲碼閱讀 6,012評論 2 31
  • 一 、java虛擬機底層結(jié)構(gòu)詳解 我們知道,一個JVM實例的行為不光是它自己的事,還涉及到它的子系統(tǒng)、存儲區(qū)域、...
    葡萄喃喃囈語閱讀 1,512評論 0 4
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,449評論 1 0
  • young generation garbage collection 整理 DefNew, ParNew, PS...
    andersonoy閱讀 1,343評論 0 1