JVM源碼分析(四)Parralel Scavenge 收集器工作流程

Parralel Scavenge 收集器工作流程

jvm初始化的時(shí)候耀找,有個(gè)重要的步驟是全局堆的初始化,根據(jù)vm參數(shù)的不同翔悠,又會(huì)選擇不同的堆實(shí)現(xiàn)(堆的實(shí)現(xiàn)在share/vm/memory中,策略選擇位于share/vm/gc_implementation, 詳情見Universe::initialize_heap())涯呻。server模式下啟動(dòng)的jvm,默認(rèn)采用的全局堆實(shí)現(xiàn)是ParallelScavengeHeap,本文記錄了jdk1.8版本的Parralel Scavenge實(shí)現(xiàn),很多細(xì)節(jié)還不清楚凉驻。

一、PS收集器概覽及初始化

ParallelScavengeHeap由兩個(gè)區(qū)域組成复罐,_old_gen和_young_gen,其中_young_gen又由eden,from雄家,to三個(gè)MutableSpace組成效诅。大致如下:

image.png

我們可以用:

  • -xmn500m 來指定新時(shí)代的大小為500m
  • -XX:SurvivorRatio=8 來指定eden區(qū)和(from+to)區(qū)占比8:1.
  • -XX:MaxTenuringThreshold=18 來指定晉升年齡

新生代的3個(gè)區(qū)域是連續(xù)的空間,做法是開辟一個(gè)虛擬內(nèi)存,根據(jù)起始大小計(jì)算分配出eden趟济,兩個(gè)survivor區(qū)域乱投。虛擬內(nèi)存不會(huì)立馬占用物理內(nèi)存,每次分配對(duì)象時(shí)載入物理內(nèi)存顷编。在首次將新生代數(shù)據(jù)晉升到年老代時(shí)物理內(nèi)存會(huì)急劇升高,(測試:在指定xmn大小為500m的情況下戚炫,每次new 4m大小空間,約400m時(shí)觸發(fā)一次 minor gc媳纬,內(nèi)存突然增長到750m)双肤。

java的new關(guān)鍵字實(shí)際會(huì)作用到j(luò)vm的申請(qǐng)內(nèi)存操作(聲明的對(duì)象名稱會(huì)在棧內(nèi)分配),調(diào)用到interpreterRuntime.cpp中的InterpreterRuntime::_new方法钮惠,為了方便觸發(fā)gc茅糜,我用了new大數(shù)組的方式來調(diào)試。
創(chuàng)建數(shù)組的入口函數(shù)有兩個(gè)素挽,一個(gè)是用于基本類型的內(nèi)存申請(qǐng)蔑赘, 另一個(gè)是對(duì)象數(shù)組的。

IRT_ENTRY(void, InterpreterRuntime::newarray(JavaThread* thread, BasicType type, jint size))
  oop obj = oopFactory::new_typeArray(type, size, CHECK);
  thread->set_vm_result(obj);
IRT_END


IRT_ENTRY(void, InterpreterRuntime::anewarray(JavaThread* thread, ConstantPool* pool, int index, jint size))
  // Note: no oopHandle for pool & klass needed since they are not used
  //       anymore after new_objArray() and no GC can happen before.
  //       (This may have to change if this code changes!)
  Klass*    klass = pool->klass_at(index, CHECK);
  objArrayOop obj = oopFactory::new_objArray(klass, size, CHECK);
  thread->set_vm_result(obj);
IRT_END

申請(qǐng)內(nèi)存需要經(jīng)過以下幾個(gè)步驟预明。

  • 1.嘗試從tlab中分配內(nèi)存缩赛,分配成功直接返回;不成功進(jìn)入下一步撰糠。

  • 2.從垃圾收集器中分配內(nèi)存酥馍,如果沒有相關(guān)指定收集器的配置,Server模式默認(rèn)的是parralel Scavenge的方式分配窗慎。

  • 3.從新生代中分配物喷,關(guān)鍵代碼HeapWord* result = young_gen()->allocate(size);卤材,我們知道新生代實(shí)際直接存放新建對(duì)象的區(qū)域是eden區(qū),關(guān)鍵代碼HeapWord* result = eden_space()->cas_allocate(word_size);

  • 4.內(nèi)存的實(shí)際分配是MutableSpace類實(shí)現(xiàn)的峦失,判斷當(dāng)前剩余空閑內(nèi)存是否足夠放下申請(qǐng)的對(duì)象扇丛,如果可以,那么成功返回尉辑。如果不行帆精,進(jìn)入步驟5。

  • 5.如果對(duì)象的大小大于eden區(qū)的一半隧魄,那么直接分配到老年代中去卓练,否則進(jìn)入下一步。


HeapWord* ParallelScavengeHeap::mem_allocate_old_gen(size_t size) {
  if (!should_alloc_in_eden(size) || GC_locker::is_active_and_needs_gc()) {
    // Size is too big for eden, or gc is locked out.
    return old_gen()->allocate(size);
  }

  // If a "death march" is in progress, allocate from the old gen a limited
  // number of times before doing a GC.
  if (_death_march_count > 0) {
    if (_death_march_count < 64) {
      ++_death_march_count;
      return old_gen()->allocate(size);
    } else {
      _death_march_count = 0;
    }
  }
  return NULL;
}

inline bool ParallelScavengeHeap::should_alloc_in_eden(const size_t size) const
{
  const size_t eden_size = young_gen()->eden_space()->capacity_in_words();
  return size < eden_size / 2;
}

  • 6.觸發(fā)一個(gè)VM_ParallelGCFailedAllocation任務(wù)拋給vm線程,這里注意下VMThread的execute方法是阻塞的购啄,需要等到vm線程的gc任務(wù)完成當(dāng)前線程才會(huì)返回襟企。默認(rèn)的任務(wù)是VM_ParallelGCFailedAllocation。查看其doit方法

void VM_ParallelGCFailedAllocation::doit() {
  SvcGCMarker sgcm(SvcGCMarker::MINOR);

  ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();
  assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "must be a ParallelScavengeHeap");

  GCCauseSetter gccs(heap, _gc_cause);
  _result = heap->failed_mem_allocate(_word_size);

  if (_result == NULL && GC_locker::is_active_and_needs_gc()) {
    set_gc_locked();
  }
}


HeapWord* ParallelScavengeHeap::failed_mem_allocate(size_t size) {
  assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint");
  assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
  assert(!Universe::heap()->is_gc_active(), "not reentrant");
  assert(!Heap_lock->owned_by_self(), "this thread should not own the Heap_lock");

  // We assume that allocation in eden will fail unless we collect.

  // First level allocation failure, scavenge and allocate in young gen.
  GCCauseSetter gccs(this, GCCause::_allocation_failure);
  const bool invoked_full_gc = PSScavenge::invoke();
  HeapWord* result = young_gen()->allocate(size);

  // Second level allocation failure.
  //   Mark sweep and allocate in young generation.
  if (result == NULL && !invoked_full_gc) {
    do_full_collection(false);
    result = young_gen()->allocate(size);
  }

  death_march_check(result, size);

  // Third level allocation failure.
  //   After mark sweep and young generation allocation failure,
  //   allocate in old generation.
  if (result == NULL) {
    result = old_gen()->allocate(size);
  }

  // Fourth level allocation failure. We're running out of memory.
  //   More complete mark sweep and allocate in young generation.
  if (result == NULL) {
    do_full_collection(true);
    result = young_gen()->allocate(size);
  }

  // Fifth level allocation failure.
  //   After more complete mark sweep, allocate in old generation.
  if (result == NULL) {
    result = old_gen()->allocate(size);
  }

  return result;
}

  • 7.可以看到狮含,先執(zhí)行一次minor gc顽悼,然后嘗試在新生代分配。如果不成功且minor gc執(zhí)行過程中沒有去full gc几迄,那么要來一次fullgc,嘗試從新生代中分配蔚龙。 還不成功,再來一次fullgc(帶清楚軟引用的),從新生代中分配映胁。依然不成功木羹,從老年代分配。還不成功解孙?OOM吧坑填。。

重點(diǎn)是const bool invoked_full_gc = PSScavenge::invoke();貼一下實(shí)現(xiàn),先執(zhí)行一次minor gc妆距。不成功的話穷遂,觸發(fā)fullgc。fullgc根據(jù)配置有兩種選擇娱据,PSMarkSweep和PSParallel蚪黑。沒有CMS,因?yàn)镻arraler Scavenge收集器和他無法同時(shí)使用中剩。

bool PSScavenge::invoke() {
  assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint");
  assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
  assert(!Universe::heap()->is_gc_active(), "not reentrant");

  ParallelScavengeHeap* const heap = (ParallelScavengeHeap*)Universe::heap();
  assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "Sanity");

  PSAdaptiveSizePolicy* policy = heap->size_policy();
  IsGCActiveMark mark;

  const bool scavenge_done = PSScavenge::invoke_no_policy();
  const bool need_full_gc = !scavenge_done ||
    policy->should_full_GC(heap->old_gen()->free_in_bytes());
  bool full_gc_done = false;

  if (UsePerfData) {
    PSGCAdaptivePolicyCounters* const counters = heap->gc_policy_counters();
    const int ffs_val = need_full_gc ? full_follows_scavenge : not_skipped;
    counters->update_full_follows_scavenge(ffs_val);
  }

  if (need_full_gc) {
    GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy);
    CollectorPolicy* cp = heap->collector_policy();
    const bool clear_all_softrefs = cp->should_clear_all_soft_refs();

    if (UseParallelOldGC) {
      full_gc_done = PSParallelCompact::invoke_no_policy(clear_all_softrefs);
    } else {
      full_gc_done = PSMarkSweep::invoke_no_policy(clear_all_softrefs);
    }
  }

  return full_gc_done;
}

接下來進(jìn)入重點(diǎn)了忌穿,minor gc的具體實(shí)現(xiàn)在PSScavenge::invoke_no_policy,代碼太長不貼了结啼。簡單說下流程掠剑。

  • 1.sanity check,記錄gc前的內(nèi)存信息郊愧。

  • 2.向gc worker線程池投遞gc任務(wù)朴译,根據(jù)根節(jié)點(diǎn)搜索法保存有效的對(duì)象井佑。根節(jié)點(diǎn)有很多種,如下。注意這些gc任務(wù)是并發(fā)執(zhí)行的眠寿。

      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::universe));
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jni_handles));
      // We scan the thread roots in parallel
      Threads::create_thread_roots_tasks(q);
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::object_synchronizer));
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::flat_profiler));
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::management));
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::system_dictionary));
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::class_loader_data));
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jvmti));
      q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::code_cache));

可達(dá)性分析算法實(shí)現(xiàn)都差不多躬翁,步驟是遍歷根節(jié)點(diǎn)的所有可達(dá)對(duì)象,判斷對(duì)象是不是在新生代(根據(jù)內(nèi)存地址判斷)盯拱,是的話調(diào)用PSPromotionManager::copy_to_survivor_space轉(zhuǎn)移存活對(duì)象盒发,如果指定了需要晉升或者對(duì)象的年齡(新生代對(duì)象每經(jīng)過一次minor gc加1歲)達(dá)到闕值,則拷貝到old區(qū)狡逢,否則拷貝到to區(qū)宁舰。

  • 3.清空eden區(qū)和from區(qū),再將有存活的對(duì)象的to區(qū)和from區(qū)做交換(保證存活對(duì)象放在from區(qū)奢浑,這樣下次gc的時(shí)候就可以再次執(zhí)行復(fù)制算法將對(duì)象拷貝到to區(qū)了)蛮艰。

    1. 調(diào)用resize_young_gen方法重新分配新時(shí)代大小(疑問:看注釋是minor gc會(huì)引起新生代大小的變化,具體什么情況殷费?)
  • 4.如果有對(duì)象晉升失敗了印荔,那么可能是old區(qū)空間不足了,此時(shí)需要觸發(fā)一次full gc,如果老年代的策略是ps old,那么處理老年代的gc由類PSParallelCompact,這一段后續(xù)分析详羡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嘿悬,隨后出現(xiàn)的幾起案子实柠,更是在濱河造成了極大的恐慌,老刑警劉巖善涨,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窒盐,死亡現(xiàn)場離奇詭異,居然都是意外死亡钢拧,警方通過查閱死者的電腦和手機(jī)蟹漓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來源内,“玉大人葡粒,你說我怎么就攤上這事∧さ觯” “怎么了嗽交?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颂斜。 經(jīng)常有香客問我夫壁,道長,這世上最難降的妖魔是什么沃疮? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任盒让,我火速辦了婚禮梅肤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邑茄。我一直安慰自己姨蝴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布撩扒。 她就那樣靜靜地躺著似扔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搓谆。 梳的紋絲不亂的頭發(fā)上炒辉,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音泉手,去河邊找鬼黔寇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛斩萌,可吹牛的內(nèi)容都是我干的缝裤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颊郎,長吁一口氣:“原來是場噩夢啊……” “哼憋飞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姆吭,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤榛做,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后内狸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體检眯,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年昆淡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锰瘸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昂灵,死狀恐怖避凝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情倔既,我是刑警寧澤恕曲,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站渤涌,受9級(jí)特大地震影響佩谣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜实蓬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一茸俭、第九天 我趴在偏房一處隱蔽的房頂上張望吊履。 院中可真熱鬧,春花似錦调鬓、人聲如沸艇炎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缀踪。三九已至,卻和暖如春虹脯,著一層夾襖步出監(jiān)牢的瞬間驴娃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工循集, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唇敞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓咒彤,卻偏偏與公主長得像疆柔,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镶柱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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