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)

- (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ù)具體是怎么做的;

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

看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的兩種情況:


** 總結一下再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é):

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