GC part 1:GC是怎么開始工作的

part 1

首選想探索一下GC是怎么開始工作的,或者說,GC到底是以什么樣的方式在工作的;java應用在啟動的時候會創(chuàng)建一個jvm進程,JVM內部通過調用create_vm來實現(xiàn),該方法做了大量的工作來創(chuàng)建一個jvm進程,并且將java應用的main方法啟動起來,運行在main線程中(主線程);在create_vm中,有一個地方值得關注,下面是thread.cpp中create_vm方法的代碼片段:

  // Create the VMThread
  { TraceTime timer("Start VMThread", TRACETIME_LOG(Info, startuptime));

  VMThread::create();
    Thread* vmthread = VMThread::vm_thread();

    if (!os::create_thread(vmthread, os::vm_thread)) {
      vm_exit_during_initialization("Cannot create VM thread. "
                                    "Out of system resources.");
    }

    // Wait for the VM thread to become ready, and VMThread::run to initialize
    // Monitors can have spurious returns, must always check another state flag
    {
      MutexLocker ml(Notify_lock);
      os::start_thread(vmthread);
      while (vmthread->active_handles() == NULL) {
        Notify_lock->wait();
      }
    }
  }

VMThread是一種特殊的jvm線程,用于執(zhí)行比如GC等操作,java代碼的Thread和JVM里面的JavaThread對應,這一點后續(xù)再研究;上面的代碼片段首先關注【VMThread::create()】這個函數(shù)調用,在VMThread.cpp中實現(xiàn)了該函數(shù):


void VMThread::create() {
  assert(vm_thread() == NULL, "we can only allocate one VMThread");
  _vm_thread = new VMThread();

  // Create VM operation queue
  _vm_queue = new VMOperationQueue();
  guarantee(_vm_queue != NULL, "just checking");

  _terminate_lock = new Monitor(Mutex::safepoint, "VMThread::_terminate_lock", true,
                                Monitor::_safepoint_check_never);

  if (UsePerfData) {
    // jvmstat performance counters
    Thread* THREAD = Thread::current();
    _perf_accumulated_vm_operation_time =
                 PerfDataManager::create_counter(SUN_THREADS, "vmOperationTime",
                                                 PerfData::U_Ticks, CHECK);
  }
}

create函數(shù)在new了一個VMThread對象實例同時,為該VMThread創(chuàng)建了一個VMOperationQueue,VMThread有一個重要的成員叫_vm_queue,看看它的定義:


  static VMOperationQueue* _vm_queue;           // Queue (w/ policy) of VM operations

根據(jù)注釋可以將該queue理解為是VMThread的任務隊列,但是隊列內部存放的任務都是VMOperation,不能是其他類型的任務,那VMOperation是什么呢?其實有一個基類叫VM_Operation,有一個子類叫VM_GC_Operation,就是專門來做GC的任務,在對象申請內存分配失敗的時候會生成一個VM_CollectForAllocation任務來做GC,_vm_queue隊列就是用來存儲這些任務的,VMThread會不斷來check該隊列是否有任務需要執(zhí)行,這種工作模式類似于特殊的線程池,這個線程池只有一個VMThread,_vm_queue就是線程池中的任務隊列;
VMThread創(chuàng)建完成之后,create_vm函數(shù)將等到VMThread啟動成功,判斷VMThread是否已經(jīng)正常工作的標準是vmthread->active_handles() == NULL為true,也就是執(zhí)行了VMThread的run函數(shù),這里說明一下,JVM的線程實現(xiàn)方法是將一個os線程綁定到JVM線程上,所以每創(chuàng)建一個JVM線程都需要創(chuàng)建一個os線程來做綁定,不同環(huán)境下創(chuàng)建os線程的方法不一樣,比如在Mac下,就是使用bsd的方法來創(chuàng)建os線程的;回頭看create_vm函數(shù)里面的那段代碼片段,可以看到使用了os::create_thread(vmthread, os::vm_thread)來創(chuàng)建了一個os線程,在os_bsd.cpp內部的create_thread函數(shù)里面,可以看到創(chuàng)建了一個os線程,并且將該os線程綁定到了創(chuàng)建好的VMThread上,幾乎在所有的os上創(chuàng)建線程的同時需要指定一個方法入口,使得os在創(chuàng)建好了線程之后可以準備執(zhí)行相應的代碼,可以在create_thread方法里面看到下面的代碼片段:

  pthread_t tid;
    int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);

thread_native_entry就是上面提到的代碼入口,可以在thread_native_entry函數(shù)內部看到執(zhí)行了VMThread的run方法,到此create_vm函數(shù)可以繼續(xù)執(zhí)行;
接下來再回過頭來看看VMThread的run函數(shù),該函數(shù)執(zhí)行了一些線程初始化的工作,比如設置線程名稱,線程優(yōu)先級等,然后執(zhí)行了一個關鍵的方法:loop,該方法可以理解為VMThread將不斷輪詢來從自己的任務隊列_vm_queue中獲取任務來執(zhí)行,下面來仔細研究一下loop函數(shù)的關鍵步驟。

  • (1)、通過remove_next方法獲取任務,如果當前任務隊列中沒有待執(zhí)行的任務,那么remove_next函數(shù)會返回NULL,下面是remove_next函數(shù)的具體實現(xiàn)
VM_Operation* VMOperationQueue::remove_next() {
  // Assuming VMOperation queue is two-level priority queue. If there are
  // more than two priorities, we need a different scheduling algorithm.
  assert(SafepointPriority == 0 && MediumPriority == 1 && nof_priorities == 2,
         "current algorithm does not work");

  // simple counter based scheduling to prevent starvation of lower priority
  // queue. -- see 4390175
  int high_prio, low_prio;
  if (_queue_counter++ < 10) {
      high_prio = SafepointPriority;
      low_prio  = MediumPriority;
  } else {
      _queue_counter = 0;
      high_prio = MediumPriority;
      low_prio  = SafepointPriority;
  }

  return queue_remove_front(queue_empty(high_prio) ? low_prio : high_prio);
}

VM_Operation* VMOperationQueue::queue_remove_front(int prio) {
  if (queue_empty(prio)) return NULL;
  assert(_queue_length[prio] >= 0, "sanity check");
  _queue_length[prio]--;
  VM_Operation* r = _queue[prio]->next();
  assert(r != _queue[prio], "cannot remove base element");
  unlink(r);
  return r;
}
  • (2)、如果發(fā)現(xiàn)任務隊列中沒有待執(zhí)行的任務,那么VMThread不能一直傻傻的輪詢啊,就會讓自己進入等待狀態(tài)
image
  • (3)、在(2)步驟中等待多時之后,VMThread可能會被一些任務填充線程喚醒(notify),這個時候loop函數(shù)就會繼續(xù)執(zhí)行接下來的代碼,有一些Operation任務要求在safe_point執(zhí)行,比如FullGC,使用SafepointSynchronize::begin()和SafepointSynchronize::end()可以達到這個目的,就像下面這樣:
SafepointSynchronize::begin()

/// safe point code 

SafepointSynchronize::end();

無論如何,接下來就是要執(zhí)行隊列中取出來的任務了,所以evaluate_operation(_cur_vm_operation)方法應該是我們接下來應該關注的;在evaluate_operation函數(shù)內部看到了調用了evaluate()函數(shù),接著看看evaluate函數(shù);

void VM_Operation::evaluate() {
  ResourceMark rm;
  outputStream* debugstream;
  bool enabled = log_is_enabled(Debug, vmoperation);
  if (enabled) {
    debugstream = Log(vmoperation)::debug_stream();
    debugstream->print("begin ");
    print_on_error(debugstream);
    debugstream->cr();
  }
  doit();
  if (enabled) {
    debugstream->print("end ");
    print_on_error(debugstream);
    debugstream->cr();
  }
}

關鍵的是doit()函數(shù),這里面就是具體的任務執(zhí)行內容,不同的Operation的doit內容都是不一樣的,就算是GC_Opertion,還是有多種不同的方式的,比如上面提到了VM_GenCollectForAllocation的doit內容做的工作就是這樣的:

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

  GenCollectedHeap* gch = GenCollectedHeap::heap();
  GCCauseSetter gccs(gch, _gc_cause);
  _result = gch->satisfy_failed_allocation(_word_size, _tlab);
  assert(gch->is_in_reserved_or_null(_result), "result not in heap");

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

gch->satisfy_failed_allocation就是為了解決空間分配失敗的,去看satisfy_failed_allocation函數(shù)的注釋,可以看到:

  // Callback from VM_GenCollectForAllocation operation.
  // This function does everything necessary/possible to satisfy an
  // allocation request that failed in the youngest generation that should
  // have handled it (including collection, expansion, etc.)
  HeapWord* satisfy_failed_allocation(size_t size, bool is_tlab);

這個函數(shù)會被VM_GenCollectForAllocation執(zhí)行的時候回調,也就是doit函數(shù)執(zhí)行的時候調用這個函數(shù),這個函數(shù)會做類似于垃圾收集,堆擴展等工作來滿足一個"allocation request";當然,回調這個函數(shù)之前必然已經(jīng)嘗試進行空間分配申請了,并且已經(jīng)失敗了,所以該函數(shù)需要極盡所能去做工作來騰出空間(申請新的空間)來滿足已經(jīng)失敗的空間分配申請;collectorPolicy類實現(xiàn)了垃圾收集的策略,所謂垃圾收集策略就是應該在什么時候做GC,做什么類型的GC等,參考價值很大;下面可以試著來看一下satisfy_failed_allocation函數(shù)具體是怎么做的;

image

從上面這張圖可以看到,如果發(fā)現(xiàn)gc_lock是活動的,也就說明已經(jīng)有其他的線程觸發(fā)了GC,那么這個時候策略就是擴展堆來滿足內存申請。

image

看if條件,如果增量GC是安全的,那么就執(zhí)行增量安全,所謂增量GC,就是按照從輕到重的程度來做垃圾回收,大概分這么幾個級別,首先是進行一次MinorGC,其次是進行一次FullGC,最后是進行一次帶soft reference清理的FullGC;上面的圖片對應的是第一種情況,進行一次MinorGC,然后嘗試申請空間,如果成功就打住了,否則就要進行一次清理soft reference的FullGC了,碩大soft reference,可以大概說一下,java中的引用分四個級別,strong reference > soft reference > weak reference > phantom reference;強度梯度下降,strong reference只要對象還在被引用就不會被回收,而soft reference就不一樣了,JVM在嘗試進行GC來解決內存不足的狀況下,如果發(fā)現(xiàn)還是無法滿足內存申請,那么就會將這部分引用類型的對象回收回來,所以,在使用soft reference的時候不應該強依賴于對象,因為不知道什么時候就被回收了,這種引用可以用在緩存的場景中;weak reference的強度比soft弱一些,它只能存活到下次GC發(fā)生,而phantom reference就更弱了,弱到你根本無法獲取到一個phantom reference對象,它唯一的作用就是可以在發(fā)生GC的時候告訴你它已經(jīng)被回收了;下面的代碼展示了進行FullGC的兩種情況:

image
image

** 總結一下再allocate fail的應對策略,首先判斷是否有其他線程觸發(fā)了GC操作,如果是的話則不會進行GC操作,而是嘗試去擴展堆來解決allocate fail,否則判斷是否可以進行增量GC,如果可以,那么執(zhí)行一次MinorGC,否則執(zhí)行一次不回收soft reference的FullGC,之后判斷是否可以解決allocate fail了,如果可以了就到此打住,否則進行一次徹底的FullGC,也就是將soft reference也回收回來 **

其實在(3)的時候,漏掉一個細節(jié),VMThread會將任務隊列中填充好的任務都執(zhí)行完成,才會繼續(xù)執(zhí)行接下來的代碼;最后希望能看一下到底在什么地方會將任務填充到VMThread的任務隊列中去;還是拿VM_GenCollectForAllocation來說,可以在collectorPolicy.cpp中的mem_allocate_work看到執(zhí)行了類似下面的代碼:

    VM_GenCollectForAllocation op(size, is_tlab, gc_count_before);
    VMThread::execute(&op);

然后又回來VMThread看execute方法,可以看到下面的細節(jié):

image

到此,GC任務是怎么運行的大概梳理了一下,具體的GC細節(jié)還是需要再梳理。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • part 1 首選想探索一下GC是怎么開始工作的,或者說,GC到底是以什么樣的方式在工作的;java應用在啟動的時...
    一字馬胡閱讀 469評論 0 0
  • part 5 本comment希望能系統(tǒng)的探索一下GC發(fā)生的時機,以及各個GC的具體工作內容(流程),GC包括Mi...
    一字馬胡閱讀 609評論 0 0
  • 表情是什么,我認為表情就是表現(xiàn)出來的情緒。表情可以傳達很多信息。高興了當然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,884評論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 8,164評論 0 4

友情鏈接更多精彩內容