1. 程式人生 > >[Inside HotSpot] Serial垃圾回收器 (二) Minor GC

[Inside HotSpot] Serial垃圾回收器 (二) Minor GC

Serial垃圾回收器Minor GC

1. DefNewGeneration垃圾回收

新生代使用複製演算法做垃圾回收,比老年代的標記-壓縮簡單很多,所有回收程式碼都位於DefNewGeneration::collect:

// hotspot\share\gc\serial\defNewGeneration.cpp
void DefNewGeneration::collect(bool   full,
                               bool   clear_all_soft_refs,
                               size_t size,
                               bool   is_tlab) {
  SerialHeap* heap = SerialHeap::heap();
  _old_gen = heap->old_gen();
  // 如果新生代全是存活物件,老年代可能容不下新生代的晉升
  // 則設定增量垃圾回收失敗,直接返回
  if (!collection_attempt_is_safe()) {
    heap->set_incremental_collection_failed(); 
    return;
  }
  ...
  // 各種閉包初始化
  IsAliveClosure is_alive(this);
  ...
  
  {
    // 掃描GC Root,用快速掃描閉包做物件複製
    StrongRootsScope srs(0);
    heap->young_process_roots(&srs,
                              &fsc_with_no_gc_barrier,
                              &fsc_with_gc_barrier,
                              &cld_scan_closure);
  }
  // 用快速成員處理閉包處理非GC Root直達物件
  evacuate_followers.do_void();
  // 特殊處理軟引用,弱引用,虛引用,final引用
 ...

  // 如果晉升成功,則清空eden,from;交換from,to分割槽;調整老年代晉升閾值
  // 同時還需要確保晉升成功的情況下to區一定是空的
  if (!_promotion_failed) {
    eden()->clear(SpaceDecorator::Mangle);
    from()->clear(SpaceDecorator::Mangle);
    if (ZapUnusedHeapArea) {
      to()->mangle_unused_area();
    }
    swap_spaces();
    adjust_desired_tenuring_threshold();
    AdaptiveSizePolicy* size_policy = heap->size_policy();
    size_policy->reset_gc_overhead_limit_count();
  } 
  // 否則晉升失敗,提醒老年代
  else {
    _promo_failure_scan_stack.clear(true); 
    remove_forwarding_pointers();
    log_info(gc, promotion)("Promotion failed");
    swap_spaces();
    from()->set_next_compaction_space(to());
    heap->set_incremental_collection_failed();
    _old_gen->promotion_failure_occurred();
  }
  // 更新gc日誌,清除preserved mar
  ...
}

在做Minor GC之前會檢查此次垃圾回收是否安全(collection_attempt_is_safe),所謂是否安全是指最壞情況下新生代全是需要晉升的存活物件,這時候老年代能否安全容納下。如果JVM回答可以做垃圾回收,那麼再做下面的展開。

2. 快速掃描閉包(FastScanClosure)

新生代的複製動作主要位於young_process_roots(),該函式首先會掃描所有型別的GC Root,使用快速掃描閉包配合GC Root將直達的存活物件複製到To survivor區,然後再掃描從老年代指向新生代的應用。快速掃描閉包指的是FastScanClosure,它的程式碼如下:

// hotspot\share\gc\shared\genOopClosures.inline.hpp
inline void FastScanClosure::do_oop(oop* p)       { FastScanClosure::do_oop_work(p); }
template <class T> inline void FastScanClosure::do_oop_work(T* p) {
  // 從地址p處獲取物件 
  T heap_oop = RawAccess<>::oop_load(p);
  if (!CompressedOops::is_null(heap_oop)) {
    oop obj = CompressedOops::decode_not_null(heap_oop);
    // 如果物件位於新生代
    if ((HeapWord*)obj < _boundary) {
      // 如果物件有轉發指標(相當於已複製過)就保持原位
      // 否則根據情況進行復制
      oop new_obj = obj->is_forwarded() ? obj->forwardee()
                                        : _g->copy_to_survivor_space(obj);
      RawAccess<IS_NOT_NULL>::oop_store(p, new_obj);
      if (is_scanning_a_cld()) {
        do_cld_barrier();
      } else if (_gc_barrier) {
        // 根據情況設定gc barrier
        do_barrier(p);
      }
    }
  }
}

一句話總結,快速掃描閉包的能力是視情況複製地址所指物件或者晉升它。這段程式碼有兩個值得提及的地方:

  1. 根據情況進行復制的copy_to_survivor_space()
  2. 根據情況設定gc屏障的do_barrier()

2.1 新生代到To survivor的複製

先說第一個複製:

// hotspot\share\gc\serial\defNewGeneration.cpp
oop DefNewGeneration::copy_to_survivor_space(oop old) {
  size_t s = old->size();
  oop obj = NULL;
  // 如果物件還年輕就在to區分配空間
  if (old->age() < tenuring_threshold()) {
    obj = (oop) to()->allocate_aligned(s);
  }
  // 如果物件比較老或者to區分配失敗,晉升到老年代
  if (obj == NULL) {
    obj = _old_gen->promote(old, s);
    if (obj == NULL) { // 晉升失敗處理
      handle_promotion_failure(old);
      return old;
    }
  } else {
    // 如果to分配成功,在新分配的空間裡面放入物件
    const intx interval = PrefetchCopyIntervalInBytes;
    Prefetch::write(obj, interval);
    Copy::aligned_disjoint_words((HeapWord*)old, (HeapWord*)obj, s);
    // 物件年齡遞增且加入年齡表
    obj->incr_age();
    age_table()->add(obj, s);
  }
  // 把新地址插入物件mark word,表示該物件已經複製過了。
  old->forward_to(obj);
  return obj;
}

程式碼很清晰,如果GC Root裡面引用的物件年齡沒有超過晉升閾值,就把它從新生代(Eden+From)轉移到To,如果超過閾值直接從新生代轉移到老年代。

2.2 GC屏障

然後說說gc barrier。之前文章提到過老年代(TenuredGeneration,久任代)繼承自卡表代(CardGeneration),卡表代把堆空間劃分為一張張512位元組的卡片,如果某個卡是髒卡(dirty card)就表示該卡表示的512位元組記憶體空間存在指向新生代的物件,就需要掃描這篇區域。do_barrier()會檢查是否開啟gc barrier,是否老年代地址p指向的物件存在指向新生代的物件。如果條件都滿足就會將卡標記為dirty,那麼具體是怎麼做的?

//hotspot\share\gc\shared\cardTableRS.hpp
class CardTableRS: public CardTable {
  ...
  void inline_write_ref_field_gc(void* field, oop new_val) {
    jbyte* byte = byte_for(field);
    *byte = youngergen_card;
  }
}

field表示這個老年代物件的地址,byte_for()會找到該地址對應的card,然後*byte = youngergen_card標記為髒卡,再來看看byte_for()又是怎麼根據地址找到card的:

//hotspot\share\gc\shared\cardTable.hpp
class CardTable: public CHeapObj<mtGC> {
  ...
  jbyte* byte_for(const void* p) const {
    jbyte* result = &_byte_map_base[uintptr_t(p) >> card_shift];
    return result;
  }
}

card_shift表示常量9,卡表是一個位元組陣列,每個位元組對映老年代512位元組,計算方法就是當前地址除以512向下取整,然後查詢卡表陣列對應的位元組:

3. 快速成員處理閉包(FastEvacuateFollowersClosure)

不難看出,快速掃描閉包只是複製和晉升了GC Root直接可達的物件引用。但問題是物件還可能有成員,可達性分析是從GC Root出發尋找物件引用,以及物件成員的引用,物件成員的成員的引用...快速成員處理閉包正是處理剩下不那麼直接的物件引用:

//hotspot\share\gc\serial\defNewGeneration.cpp
void DefNewGeneration::FastEvacuateFollowersClosure::do_void() {
  do {
    // 對整個堆引用快速成員處理閉包,注意快速掃描閉包是不能單獨行動的
    // 他還需要藉助快速掃描閉包的力量,因為快速掃描閉包有複製物件的能力
    // _scan_cur_or_nonheap表示快速掃描閉包
    // _scan_older表示帶gc屏障的快速掃描閉包
    _heap->oop_since_save_marks_iterate(_scan_cur_or_nonheap, _scan_older);
  } while (!_heap->no_allocs_since_save_marks());
}

第一步快速掃描閉包可能會將Eden+From區的物件提升到老年代,這時候如果只處理新生代是不夠的,因為這些提升了的物件可能還有新生代的成員域,所以快速成員處理閉包作用的是除了To survivor的整個堆(Eden+From+Tenured)。

//hotspot\share\gc\shared\space.inline.hpp
template <typename OopClosureType>
void ContiguousSpace::oop_since_save_marks_iterate(OopClosureType* blk) {
  HeapWord* t;
  // 掃描指標為灰色物件開始
  HeapWord* p = saved_mark_word();
  const intx interval = PrefetchScanIntervalInBytes;
  do {
    // 灰色物件結束
    t = top();
    while (p < t) {
      Prefetch::write(p, interval);
      oop m = oop(p);
      // 迭代處理物件m的成員&&返回物件m的大小
      // 掃描指標向前推進
      p += m->oop_iterate_size(blk);
    }
  } while (t < top());
  set_saved_mark_word(p);
}

這裡比較坑的是oop_iterate_size()函式會同時迭代處理物件m的成員並返回物件m的大小...還要注意oop_iterate_size()傳入的blk表示的是快速掃描閉包,同樣一句話總結,快速成員處理閉包的能力是遞迴式處理一個分割槽所有物件及物件成員,這種能力配合上快速掃描閉包最終效果就是將一個分割槽的物件視情況複製到到To survivor區或者晉升到老年代。

關於快速掃描閉包和快速成員處理閉包用圖片說明可能更簡單,假設有ABCD四個物件:

當快速掃描閉包完成時A假設會進入To區域:

當快速成員處理閉包完成時A的成員B和老年代C指向的成員D也會進入To:

相關推薦

[Inside HotSpot] Serial垃圾回收 () Minor GC

Serial垃圾回收器Minor GC 1. DefNewGeneration垃圾回收 新生代使用複製演算法做垃圾回收,比老年代的標記-壓縮簡單很多,所有回收程式碼都位於DefNewGeneration::collect: // hotspot\share\gc\serial\defNewGeneration

[Inside HotSpot] Serial垃圾回收Full GC

0. Serial垃圾回收器Full GC Serial垃圾回收器的Full GC使用標記-壓縮(Mark-Compact)進行垃圾回收,該演算法基於Donald E. Knuth提出的Lisp2演算法,它會把所有存活物件滑動到空間的一端,所以也叫sliding compact。Full GC始於gc/ser

Hotspot垃圾回收

ref collect serial 使用 區域 rec data 是否 而是 Hotspot垃圾回收器 HotSpot虛擬機提供了多種垃圾收集器,每種收集器都有各自的特點,沒有最好的垃圾收集器,只有最適合的垃圾收集器。我們可以根據自己實際的應用需求選擇最適合的垃

深入Java虛擬機器閱讀感()-Java垃圾回收與記憶體分配策略

垃圾回收器主要演算法:       1、引用計數法。給物件新增一個計數器,當物件被使用時則加1,當引用失效時則減1,當計數為0時則認為該物件可以被回收。由於該算演算法無法解決物件相互引用而計數不會減為0,導致該物件無法回收,所以該演算法不是Java虛擬垃圾回收器

一文了解JVM全部垃圾回收,從Serial到ZGC

應用 base garbage 最大收益 監控 fill 前沿 mage 記錄 《對象搜索算法與回收算法》介紹了垃圾回收的基礎算法,相當於垃圾回收的方法論。接下來就詳細看看垃圾回收的具體實現。 上文提到過現代的商用虛擬機的都是采用分代收集的,不同的區域用不同的收集器。常用的

一文了解福運來時_時彩原始碼下載JVM全部垃圾回收,從Serial到ZGC

上文快樂十分原始碼下載+騰訊分分彩原始碼【征途原始碼論壇http://zhengtuwangluo.com】聯絡方式:QQ:2747044651提到過現代的商用虛擬機器的都是採用分代收集的,不同的區域用不同的收集器。常用的7種收集器,其適用的範圍如圖所示 Ser

kubernetes垃圾回收GarbageCollector Controller原始碼分析(

kubernetes版本:1.13.2 接上一節:kubernetes垃圾回收器GarbageCollector Controller原始碼分析(一) 主要步驟 GarbageCollector Controller原始碼主要分為以下幾部分: monitors作為生產者將變化的資源放入graphChan

JVM GC算法 垃圾回收

com 修正 可用 mark 信息 網站 最長 style 互聯網 JVM的垃圾回收算法有三種: 1.標記-清除(mark-sweep):啥都不說,直接上圖 2.標記-整理(mark-compact) 3.復制(copy) 分代收集算法

了解CMS(Concurrent Mark-Sweep)垃圾回收

最短 簡單 查看 用戶 行動 idt 虛擬 解決 嘗試 http://www.iteye.com/topic/1119491 1.總體介紹: CMS(Concurrent Mark-Sweep)是以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器。對於要求服務器響應速度的

3.垃圾回收

jvm 深入理解java虛擬機 3.1.引用計數法給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用的。但是,至少主流的Java虛擬機裏面沒有選用引用計數算法來管理內存,其中最主要的原因是它很難解決對象之間相互循

一步一步學JVM-垃圾回收

trac 部分 current 可控 吞吐量 收集器 控制 需要 但是 Serial收集器 Serial收集器是最基本、歷史最悠久的收集器。這個收集器是一個單線程的收集器。它在進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。Serial收集器是

垃圾回收的基本原理是什麽?垃圾回收可以馬上回收內存嗎?有什麽辦法主動通知虛擬機進行垃圾

是什麽 就會 使用 實例 new vector runt 內存堆 time()   對於GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達的",哪些

04_垃圾回收

gpo tro 多線程 class 新生代 有效 ron 基礎 div 【簡述】 在Java虛擬機中,垃圾回收器不僅僅只有一種,什麽情況下該使用哪種,對性能又有什麽樣的影響,這都是我們需要了解的。 主要的垃圾回收器有這麽幾種: 1.串行垃圾回收器 2.並行垃圾回收器

JVM的垃圾回收機制 總結(垃圾收集、回收算法、垃圾回收)

策略 .html clas 高並發 hotspot 指定 %20 引用關系 新增  相信和小編一樣的程序猿們在日常工作或面試當中經常會遇到JVM的垃圾回收問題,有沒有在夜深人靜的時候詳細捋一捋JVM垃圾回收機制中的知識點呢?沒時間捋也沒關系,因為小編接下來會給你捋

深入理解JAVA虛擬機閱讀筆記——垃圾回收

ont 分享 root 深入理解 .com 筆記 直接 用戶線程 另一個 一、垃圾收集器總覽 新生代:Serial、 ParNew、 Parallel Scavenge 老年代:CMS、Serial Old、 Parallel Old 最新的:G1 並行和並發的區別:

jvm參數的配置、垃圾回收的配置

cms maxperm XP 垃圾回收 初始化 PE bubuko 根據 CM (1)jvm也是在啟動文件中配 -xms:初始堆大小 -xmx:最大堆大小 -xmn:年輕代大小 -XX:PermSize:持久代大小 -XX:MaxPermSize:持久帶最大值 -Xss:每

JVM垃圾回收之G1(Garbage First)--new

g1垃圾收集器 基於 後臺 指定 維護 不用 大小 算法實現 聲明   相比CMS收集器有兩個顯著的改進:   (1)、 G1收集器是基於“標記-整理”算法實現的收集器,不會產生空間碎片   (2)、 它可以精確地控制停頓,能讓使用者明確指定在一個長度為M毫秒的時間片段

JVM系列(六) - JVM垃圾回收

-c 公眾 進階 中一 比例 但是 member 最佳實踐 block 前言 在之前的幾篇博客中,我們大致介紹了,常見的 垃圾回收算法 及 JVM 中常見的分類回收算法。這些都是從算法和規範上分析 Java 中的垃圾回收,屬於方法論。在 JVM 中,垃圾回收的具體實現是由

Jvm垃圾回收(終結篇)

掃描 star rem 提前 判斷 清除 png 重要 處的 Jvm垃圾回收目前就準備了這三篇博文進行整理,在寫博文的過程中我也是邊看邊記載的,我覺得這種學習方式更容易讓人記住,不會輕易忘記。以前的學習模式都是看PDF文檔、看書等,但是有個缺點就是當時記住了過段時間就會忘記

JAVA垃圾回收垃圾回收演算法

垃圾回收演算法 1 標記演算法 11 引用計數演算法 12 可達性分析演算法 2 回收演算法 21 標記-清除