深刻理解Java之垃圾回收

概述

因爲JVM中垃圾收集器的存在,使得Java程序員在開發過程當中能夠不用關心對象建立時的內存分配以及釋放過程,當內存不足時,JVM會自動開啓垃圾收集線程,進行垃圾對象的回收。
那麼垃圾回收線程究竟是何時觸發,並如何實現垃圾回收的呢?本文將對openjdk的源碼進行分析,並經過代碼分析Java垃圾回收的過程。java

VMThread

VMThread主要負責調度執行虛擬機內部的VM線程操做,如GC操做等,在JVM實例建立時進行初始化。
這裏寫圖片描述程序員

VMThread::create()

VMThread::create()方法負責該線程的建立。
這裏寫圖片描述web

在create方法裏主要執行兩個事情:算法

  1. VMThread內部維護了一個VMOperationQueue類型的隊列,用於保存內部提交的VM線程操做VM_operation,在VMThread建立時會對該隊列進行初始化。
  2. 因爲VMThread自己就是一個線程,啓動後經過執行loop方法進行輪詢操做,從隊列中按照優先級取出當前須要執行的VM_operation對象並執行。

其中整個現成的輪詢過程分爲兩步:
第一步
這裏寫圖片描述安全

若是隊列爲空,_vm_queue->remove_next()方法則返回空的_cur_vm_operation,不然根據隊列中的VM_operation優先級進行從新排序,並返回隊列頭部的VM_operation。若是_cur_vm_operation爲空,則執行以下邏輯:
這裏寫圖片描述併發

經過執行VMOperationQueue_lock->wait方法等待VM operation。jvm

第二步
這裏寫圖片描述svg

若是當前vm_operation須要在安全點執行,如FULL GC,則執行上述邏輯,不然執行如下邏輯:
這裏寫圖片描述
經過evaluate_operation執行當前的_cur_vm_operation,最終調用vm_operation對象的evaluate方法。
這裏寫圖片描述
子類經過重寫VM_Operation類的doit方法實現具體的邏輯。oop

Java gc觸發

在Java的內存分配機制中,當新生代不足以分配對象所需的內存時,會觸發一次YGC,具體實現以下:
這裏寫圖片描述post

上面這段代碼的意思是建立一個VM_GenCollectForAllocation類型的VM_Operation,經過執行VMThread::execute方法保存到VMThread的隊列中,其中execute的核心實現以下:
這裏寫圖片描述

YGC的VM_Operation加入到隊列後,經過執行VMOperationQueue_lock的notify方法喚醒VMThread線程,等待被執行,其中VM_GenCollectForAllocation的doit方法實現:
這裏寫圖片描述

經過VMThread調度執行gc操做,最終調用對應的doit方法:
一、利用SvcGCMarker通知minor gc操做的開始;
二、設置觸發gc的緣由爲GCCause::_allocation_failure,即內存分配失敗;
三、其中GenCollectedHeap的satisfy_failed_allocation方法會調用GC策略的satisfy_failed_allocation方法,處理內存分配失敗的狀況;

satisfy_failed_allocation
這裏寫圖片描述
若是其它線程觸發了gc操做,則經過擴展內存代的容量進行分配,最後無論有沒有分配成功都返回,等待其它線程的gc操做結束;

這裏寫圖片描述

若是增量式gcincremental collection可行,則經過do_collection方法執行一次minor gc,即回收新生代的垃圾。

這裏寫圖片描述
若是增量式gc不可行,則經過do_collection方法執行一次full gc。
這裏寫圖片描述

gc結束以後,再次從內存堆的各個內存代中依次分配指定大小的內存塊,若是分配成功則返回,不然繼續。

這裏寫圖片描述
若是gc結束後仍是分配失敗,說明gc失敗了,則再次嘗試經過容許擴展內存代容量的方式來試圖分配指定大小的內存塊。
這裏寫圖片描述
若是執行到這一步,說明gc以後仍是內存不足,則經過do_collection方法最後再進行一次完全的gc,回收全部的內存代,對堆內存進行壓縮,且清除軟引用。
這裏寫圖片描述

通過一次完全的gc以後,最後一次嘗試依次從各內存代分配指定大小的內存塊。

:從上述分析中能夠發現,gc操做的入口都位於GenCollectedHeap::do_collection方法中,不一樣的參數執行不一樣類型的gc。

這裏寫圖片描述

do_collection實現

這裏寫圖片描述

執行gc操做必須知足四個條件:
一、在一個同步安全點,VMThread在調用gc操做時會經過SafepointSynchronize::begin/end方法實現進出安全區域,調用begin方法時會強制全部線程到達一個安全點;
二、當前線程是VM線程或併發的gc線程;
三、當前線程已經得到內存堆的全局鎖;
四、內存堆當前_is_gc_active參數爲false,即還未開始gc;

這裏寫圖片描述

若是當前有其它線程觸發了gc,則終止當前的gc線程,不然繼續。

這裏寫圖片描述

根據參數do_clear_all_soft_refs和GC策略判斷本次gc是否須要清除軟引用;記錄當前永久代的使用量perm_prev_used;若是啓動參數中設置了-XX:+PrintHeapAtGC,則打印GC發生時內存堆的信息。

這裏寫圖片描述

一、設置參數_is_gc_active爲真,表示當前線程正式開始gc操做;
二、判斷當前是否要進行一次full gc,並肯定觸發full gc的緣由,如經過調用System.gc()觸發;
三、若是設置了PrintGC和PrintGCDateStamps,則在輸出日誌中添加時間戳;
四、若是設置了PrintGCDetails,則打印本次gc的詳細CPU耗時,如 user_time、system_time和real_time;
五、gc_prologue方法在gc開始前作一些前置處理,如設置每一個內存代的_soft_end字段;
六、更新發生gc的次數_total_collections,若是當前gc是full gc,則還需更新發生full gc的次數_total_full_collections;

這裏寫圖片描述

獲取當前內存堆的使用量gch_prev_used;初始化開始回收的內存代序號starting_level,默認爲0,即從最年輕的內存代開始;若是當前gc是full gc,則從最老的內存代開始向前搜索,找到第一個可收集全部新生代的內存代,稍後從該內存代開始回收;

這裏寫圖片描述

從序號爲starting_level的內存代開始回收;若是當前內存代不須要進行回收,則處理下一個內存代,不然對當前內存進行回收;若是當前內存代全部內存代中最老的,則將本次的gc過程升級爲full gc,更新full gc的次數,並執行full gc的前置處理。

這裏寫圖片描述

一、若是設置了參數HeapDumpBeforeFullGC,則對內存堆進行dump;
二、若是設置了參數PrintClassHistogramBeforeFullGC,則打印在進行FGC以前的對象;

這裏寫圖片描述

一、統計各個內存代進行gc時的數據;
二、若是開啓了ZapUnusedHeapArea,則在回收每一個內存代時都要對內存代的內存上限地址top進行更新;

這裏寫圖片描述

到這一步纔開始真正的gc操做:設置當前內存代的_saved_mark值,即設置這些內存區域塊的上限地址;經過每一個內存代管理器的collect方法對垃圾對象的進行回收,垃圾收集算法的具體細節會在後文進行分析;

這裏寫圖片描述

一、若是當前是FGC,則調用post_full_gc_dump方法通知gc已經完成,能夠進行後續操做,若是設置了參數HeapDumpAfterFullGC,則在gc後能夠對堆內存進行dump;若是設置了參數PrintClassHistogramAfterFullGC,則在gc後能夠打印存活的對象;
二、若是設置了參數PrintGCDetails,則在gc後能夠打印內存堆的變化狀況;若是當前仍是FGC,則還能夠打印永久代的內存變化狀況。

這裏寫圖片描述

gc完成後,調整內存堆中各內存代的大小;若是是FGC,則還須要調整永久代大小;獲取FullGCCount_lock鎖,對_full_collections_completed進行更新,並經過鎖機制通知本次FGC已經完成;

這裏寫圖片描述

打印內存堆的gc總次數和FGC次數;ExitAfterGCNum默認是0,若是設置ExitAfterGCNum大於0,且gc的總次數超過ExitAfterGCNum,則終止整個JVM進程。到此Java jvm垃圾回收進程就終止gc進程。

本文同步分享在 博客「xiangzhihong8」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索