本文基於 Java 17-ea,可是相關設計在 Java 11 以後是大體同樣的java
咱們常常在面試中詢問 System.gc()
究竟會不會馬上觸發 Full GC,網上也有不少人給出了答案,可是這些答案都有些過期了。本文基於最新的 Java 的下一個即將發佈的 LTS 版本 Java 17(ea)的源代碼,深刻解析 System.gc() 背後的故事。git
System.gc()
JVM 的內存,不止堆內存,還有其餘不少塊,經過 Native Memory Tracking 能夠看到:github
Native Memory Tracking: Total: reserved=6308603KB, committed=4822083KB - Java Heap (reserved=4194304KB, committed=4194304KB) (mmap: reserved=4194304KB, committed=4194304KB) - Class (reserved=1161041KB, committed=126673KB) (classes #21662) ( instance classes #20542, array classes #1120) (malloc=3921KB #64030) (mmap: reserved=1157120KB, committed=122752KB) ( Metadata: ) ( reserved=108544KB, committed=107520KB) ( used=105411KB) ( free=2109KB) ( waste=0KB =0.00%) ( Class space:) ( reserved=1048576KB, committed=15232KB) ( used=13918KB) ( free=1314KB) ( waste=0KB =0.00%) - Thread (reserved=355251KB, committed=86023KB) (thread #673) (stack: reserved=353372KB, committed=84144KB) (malloc=1090KB #4039) (arena=789KB #1344) - Code (reserved=252395KB, committed=69471KB) (malloc=4707KB #17917) (mmap: reserved=247688KB, committed=64764KB) - GC (reserved=199635KB, committed=199635KB) (malloc=11079KB #29639) (mmap: reserved=188556KB, committed=188556KB) - Compiler (reserved=2605KB, committed=2605KB) (malloc=2474KB #2357) (arena=131KB #5) - Internal (reserved=3643KB, committed=3643KB) (malloc=3611KB #8683) (mmap: reserved=32KB, committed=32KB) - Other (reserved=67891KB, committed=67891KB) (malloc=67891KB #2859) - Symbol (reserved=26220KB, committed=26220KB) (malloc=22664KB #292684) (arena=3556KB #1) - Native Memory Tracking (reserved=7616KB, committed=7616KB) (malloc=585KB #8238) (tracking overhead=7031KB) - Arena Chunk (reserved=10911KB, committed=10911KB) (malloc=10911KB) - Tracing (reserved=25937KB, committed=25937KB) (malloc=25937KB #8666) - Logging (reserved=5KB, committed=5KB) (malloc=5KB #196) - Arguments (reserved=18KB, committed=18KB) (malloc=18KB #486) - Module (reserved=532KB, committed=532KB) (malloc=532KB #3579) - Synchronizer (reserved=591KB, committed=591KB) (malloc=591KB #4777) - Safepoint (reserved=8KB, committed=8KB) (mmap: reserved=8KB, committed=8KB)
-Xmx
限制的最大堆大小的內存。-XX:MaxMetaspaceSize
限制最大大小,另外是 class space,被-XX:CompressedClassSpaceSize
限制最大大小-Xss
限制,可是總大小沒有限制。 -XX:ReservedCodeCacheSize
限制-XX:StringTableSize
個數限制,總內存大小不受限制除了 Native Memory Tracking 記錄的內存使用,還有兩種內存 Native Memory Tracking 沒有記錄,那就是:面試
針對除了堆內存之外,其餘的內存,有些也是須要 GC 的。例如:MetaSpace,CodeCache,Direct Buffer,MMap Buffer 等等。早期在 Java 8 以前的 JVM,對於這些內存回收的機制並不完善,不少狀況下都須要 FullGC 掃描整個堆才能肯定這些區域中哪些內存能夠回收。編程
有一些框架,大量使用並管理了這些堆外空間。例如 netty 使用了 Direct Buffer,Kafka 和 RocketMQ 使用了 Direct Buffer 和 MMap Buffer。他們都是提早從系統申請好一塊內存,以後管理起來並使用。在空間不足時,繼續向系統申請,而且也會有縮容。例如 netty,在使用的 Direct Buffer 達到-XX:MaxDirectMemorySize
的限制以後,則會先嚐試將不可達的Reference對象加入Reference鏈表中,依賴Reference的內部守護線程觸發能夠被回收DirectByteBuffer關聯的Cleaner的run()方法。若是內存仍是不足, 則執行System.gc()
,指望觸發full gc
,來回收堆內存中的DirectByteBuffer
對象來觸發堆外內存回收,若是仍是超過限制,則拋出java.lang.OutOfMemoryError
.微信
對於 WeakReference,只要發生 GC,不管是 Young GC 仍是 FullGC 就會被回收。SoftReference 只有在 FullGC 的時候纔會被回收。當咱們程序想主動對於這些引用進行回收的時候,須要能觸發 GC 的方法,這就用到了System.gc()
。app
有些時候,咱們爲了測試,學習 JVM 的某些機制,須要讓 JVM 作一次 GC 以後開始,這也會用到System.gc()
。可是其實有更好的方法,後面你會看到。框架
System.gc()
背後的原理System.gc()
實際上調用的是RunTime.getRunTime().gc()
:jvm
public static void gc() { Runtime.getRuntime().gc(); }
這個方法是一個 native 方法:async
public native void gc();
對應 JVM 源碼:
JVM_ENTRY_NO_ENV(void, JVM_GC(void)) JVMWrapper("JVM_GC"); //若是沒有將JVM啓動參數 DisableExplicitGC 設置爲 false,則執行 GC,GC 緣由是 System.gc 觸發,對應 GCCause::_java_lang_system_gc if (!DisableExplicitGC) { Universe::heap()->collect(GCCause::_java_lang_system_gc); } JVM_END
首先,根據 DisableExplicitGC 這個 JVM 啓動參數的狀態,肯定是否會 GC,若是須要 GC,不一樣 GC 會有不一樣的處理。
若是是 System.gc()
觸發的 GC,G1 GC 會根據 ExplicitGCInvokesConcurrent 這個 JVM 參數決定是默認 GC (輕量 GC,YoungGC)仍是 FullGC。
參考代碼g1CollectedHeap.cpp
:
//是否應該並行 GC,也就是較爲輕量的 GC,對於 GCCause::_java_lang_system_gc,這裏就是判斷 ExplicitGCInvokesConcurrent 這個 JVM 是否爲 true if (should_do_concurrent_full_gc(cause)) { return try_collect_concurrently(cause, gc_count_before, old_marking_started_before); }// 省略其餘這裏咱們不關心的判斷分支 else { //不然進入 full GC VM_G1CollectFull op(gc_count_before, full_gc_count_before, cause); VMThread::execute(&op); return op.gc_succeeded(); }
直接不處理,不支持經過 System.gc()
觸發 GC。
參考源碼:zDriver.cpp
void ZDriver::collect(GCCause::Cause cause) { switch (cause) { //注意這裏的 _wb 開頭的 GC 緣由,這表明是 WhiteBox 觸發的,後面咱們會用到,這裏先記一下 case GCCause::_wb_young_gc: case GCCause::_wb_conc_mark: case GCCause::_wb_full_gc: case GCCause::_dcmd_gc_run: case GCCause::_java_lang_system_gc: case GCCause::_full_gc_alot: case GCCause::_scavenge_alot: case GCCause::_jvmti_force_gc: case GCCause::_metadata_GC_clear_soft_refs: // Start synchronous GC _gc_cycle_port.send_sync(cause); break; case GCCause::_z_timer: case GCCause::_z_warmup: case GCCause::_z_allocation_rate: case GCCause::_z_allocation_stall: case GCCause::_z_proactive: case GCCause::_z_high_usage: case GCCause::_metadata_GC_threshold: // Start asynchronous GC _gc_cycle_port.send_async(cause); break; case GCCause::_gc_locker: // Restart VM operation previously blocked by the GC locker _gc_locker_port.signal(); break; case GCCause::_wb_breakpoint: ZBreakpoint::start_gc(); _gc_cycle_port.send_async(cause); break; //對於其餘緣由,不觸發GC,GCCause::_java_lang_system_gc 會走到這裏 default: // Other causes not supported fatal("Unsupported GC cause (%s)", GCCause::to_string(cause)); break; } }
Shenandoah 的處理和 G1 GC 的相似,先判斷是否是用戶明確觸發的 GC,而後經過 DisableExplicitGC 這個 JVM 參數判斷是否能夠 GC(其實這個是多餘的,能夠去掉,由於外層JVM_ENTRY_NO_ENV(void, JVM_GC(void))
已經處理這個狀態位了)。若是能夠,則請求 GC,阻塞等待 GC 請求被處理。而後根據 ExplicitGCInvokesConcurrent 這個 JVM 參數決定是默認 GC (輕量並行 GC,YoungGC)仍是 FullGC。
參考源碼shenandoahControlThread.cpp
void ShenandoahControlThread::request_gc(GCCause::Cause cause) { assert(GCCause::is_user_requested_gc(cause) || GCCause::is_serviceability_requested_gc(cause) || cause == GCCause::_metadata_GC_clear_soft_refs || cause == GCCause::_full_gc_alot || cause == GCCause::_wb_full_gc || cause == GCCause::_scavenge_alot, "only requested GCs here"); //若是是顯式GC(即若是是GCCause::_java_lang_system_gc,GCCause::_dcmd_gc_run,GCCause::_jvmti_force_gc,GCCause::_heap_inspection,GCCause::_heap_dump中的任何一個) if (is_explicit_gc(cause)) { //若是沒有關閉顯式GC,也就是 DisableExplicitGC 爲 false if (!DisableExplicitGC) { //請求 GC handle_requested_gc(cause); } } else { handle_requested_gc(cause); } }
請求 GC 的代碼流程是:
void ShenandoahControlThread::handle_requested_gc(GCCause::Cause cause) { MonitorLocker ml(&_gc_waiters_lock); //獲取當前全局 GC id size_t current_gc_id = get_gc_id(); //由於要進行 GC ,因此將id + 1 size_t required_gc_id = current_gc_id + 1; //直到當前全局 GC id + 1 爲止,表明 GC 執行了 while (current_gc_id < required_gc_id) { //設置 gc 狀態位,會有其餘線程掃描執行 gc _gc_requested.set(); //記錄 gc 緣由,根據不一樣緣由有不一樣的處理策略,咱們這裏是 GCCause::_java_lang_system_gc _requested_gc_cause = cause; //等待 gc 鎖對象 notify,表明 gc 被執行並完成 ml.wait(); current_gc_id = get_gc_id(); } }
對於GCCause::_java_lang_system_gc
,GC 的執行流程大概是:
bool explicit_gc_requested = _gc_requested.is_set() && is_explicit_gc(_requested_gc_cause); //省略一些代碼 else if (explicit_gc_requested) { cause = _requested_gc_cause; log_info(gc)("Trigger: Explicit GC request (%s)", GCCause::to_string(cause)); heuristics->record_requested_gc(); // 若是 JVM 參數 ExplicitGCInvokesConcurrent 爲 true,則走默認輕量 GC if (ExplicitGCInvokesConcurrent) { policy->record_explicit_to_concurrent(); mode = default_mode; // Unload and clean up everything heap->set_unload_classes(heuristics->can_unload_classes()); } else { //不然,執行 FullGC policy->record_explicit_to_full(); mode = stw_full; } }
System.gc()
相關的 JVM 參數說明:是否禁用顯式 GC,默認是不由用的。對於 Shenandoah GC,顯式 GC 包括:GCCause::_java_lang_system_gc
,GCCause::_dcmd_gc_run
,GCCause::_jvmti_force_gc
,GCCause::_heap_inspection
,GCCause::_heap_dump
,對於其餘 GC,僅僅限制GCCause::_java_lang_system_gc
默認:false
舉例:若是想禁用顯式 GC:-XX:+DisableExplicitGC
說明:對於顯式 GC,是執行輕量並行 GC (YoungGC)仍是 FullGC,若是爲 true 則是執行輕量並行 GC (YoungGC),false 則是執行 FullGC
默認:false
舉例:啓用的話指定:-XX:+ExplicitGCInvokesConcurrent
其實,在設計上有人提出(參考連接)想將 ExplicitGCInvokesConcurrent 改成 true。可是目前並非全部的 GC 均可以在輕量並行 GC 對 Java 全部內存區域進行回收,有些時候必須經過 FullGC。因此,目前這個參數仍是默認爲 false
ExplicitGCInvokesConcurrentAndUnloads
和使用 ClassUnloadingWithConcurrentMark
替代若是顯式 GC採用輕量並行 GC,那麼沒法執行 Class Unloading(類卸載),若是啓用了類卸載功能,可能會有異常。因此經過這個狀態位來標記在顯式 GC時,即便採用輕量並行 GC,也要掃描進行類卸載。
ExplicitGCInvokesConcurrentAndUnloads
目前已通過期了,用ClassUnloadingWithConcurrentMark
替代
答案是經過 WhiteBox API。可是這個不要在生產上面執行,僅僅用來測試 JVM 還有學習 JVM 使用。WhiteBox API 是 HotSpot VM 自帶的白盒測試工具,將內部的不少核心機制的 API 暴露出來,用於白盒測試 JVM,壓測 JVM 特性,以及輔助學習理解 JVM 並調優參數。WhiteBox API 是 Java 7 引入的,目前 Java 8 LTS 以及 Java 11 LTS(實際上是 Java 9+ 之後的全部版本,這裏只關心 LTS 版本,Java 9 引入了模塊化因此 WhiteBox API 有所變化)都是有的。可是默認這個 API 並無編譯在 JDK 之中,可是他的實現是編譯在了 JDK 裏面了。因此若是想用這個 API,須要用戶本身編譯須要的 API,並加入 Java 的 BootClassPath 並啓用 WhiteBox API。下面咱們來用 WhiteBox API 來主動觸發各類 GC。
1. 編譯 WhiteBox API
將https://github.com/openjdk/jdk/tree/master/test/lib
路徑下的sun
目錄取出,編譯成一個 jar 包,名字假設是 whitebox.jar
2. 編寫測試程序
將 whitebox.jar
添加到你的項目依賴,以後寫代碼
public static void main(String[] args) throws Exception { WhiteBox whiteBox = WhiteBox.getWhiteBox(); //執行young GC whiteBox.youngGC(); System.out.println("---------------------------------"); whiteBox.fullGC(); //執行full GC whiteBox.fullGC(); //保持進程不退出,保證日誌打印完整 Thread.currentThread().join(); }
3. 啓動程序查看效果
使用啓動參數 -Xbootclasspath/a:/home/project/whitebox.jar -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xlog:gc
啓動程序。其中前三個 Flag 表示啓用 WhiteBox API,最後一個表示打印 GC info 級別的日誌到控制檯。
個人輸出:
[0.036s][info][gc] Using G1 [0.048s][info][gc,init] Version: 17-internal+0-adhoc.Administrator.jdk (fastdebug) [0.048s][info][gc,init] CPUs: 16 total, 16 available [0.048s][info][gc,init] Memory: 16304M [0.048s][info][gc,init] Large Page Support: Disabled [0.048s][info][gc,init] NUMA Support: Disabled [0.048s][info][gc,init] Compressed Oops: Enabled (32-bit) [0.048s][info][gc,init] Heap Region Size: 1M [0.048s][info][gc,init] Heap Min Capacity: 512M [0.048s][info][gc,init] Heap Initial Capacity: 512M [0.048s][info][gc,init] Heap Max Capacity: 512M [0.048s][info][gc,init] Pre-touch: Disabled [0.048s][info][gc,init] Parallel Workers: 13 [0.048s][info][gc,init] Concurrent Workers: 3 [0.048s][info][gc,init] Concurrent Refinement Workers: 13 [0.048s][info][gc,init] Periodic GC: Disabled [0.049s][info][gc,metaspace] CDS disabled. [0.049s][info][gc,metaspace] Compressed class space mapped at: 0x0000000100000000-0x0000000140000000, reserved size: 1073741824 [0.049s][info][gc,metaspace] Narrow klass base: 0x0000000000000000, Narrow klass shift: 3, Narrow klass range: 0x140000000 [1.081s][info][gc,start ] GC(0) Pause Young (Normal) (WhiteBox Initiated Young GC) [1.082s][info][gc,task ] GC(0) Using 12 workers of 13 for evacuation [1.089s][info][gc,phases ] GC(0) Pre Evacuate Collection Set: 0.5ms [1.089s][info][gc,phases ] GC(0) Merge Heap Roots: 0.1ms [1.089s][info][gc,phases ] GC(0) Evacuate Collection Set: 3.4ms [1.089s][info][gc,phases ] GC(0) Post Evacuate Collection Set: 1.6ms [1.089s][info][gc,phases ] GC(0) Other: 1.3ms [1.089s][info][gc,heap ] GC(0) Eden regions: 8->0(23) [1.089s][info][gc,heap ] GC(0) Survivor regions: 0->2(4) [1.089s][info][gc,heap ] GC(0) Old regions: 0->0 [1.089s][info][gc,heap ] GC(0) Archive regions: 0->0 [1.089s][info][gc,heap ] GC(0) Humongous regions: 0->0 [1.089s][info][gc,metaspace] GC(0) Metaspace: 6891K(7104K)->6891K(7104K) NonClass: 6320K(6400K)->6320K(6400K) Class: 571K(704K)->571K(704K) [1.089s][info][gc ] GC(0) Pause Young (Normal) (WhiteBox Initiated Young GC) 7M->1M(512M) 7.864ms [1.089s][info][gc,cpu ] GC(0) User=0.00s Sys=0.00s Real=0.01s --------------------------------- [1.091s][info][gc,task ] GC(1) Using 12 workers of 13 for full compaction [1.108s][info][gc,start ] GC(1) Pause Full (WhiteBox Initiated Full GC) [1.108s][info][gc,phases,start] GC(1) Phase 1: Mark live objects [1.117s][info][gc,phases ] GC(1) Phase 1: Mark live objects 8.409ms [1.117s][info][gc,phases,start] GC(1) Phase 2: Prepare for compaction [1.120s][info][gc,phases ] GC(1) Phase 2: Prepare for compaction 3.031ms [1.120s][info][gc,phases,start] GC(1) Phase 3: Adjust pointers [1.126s][info][gc,phases ] GC(1) Phase 3: Adjust pointers 5.806ms [1.126s][info][gc,phases,start] GC(1) Phase 4: Compact heap [1.190s][info][gc,phases ] GC(1) Phase 4: Compact heap 63.812ms [1.193s][info][gc,heap ] GC(1) Eden regions: 1->0(25) [1.193s][info][gc,heap ] GC(1) Survivor regions: 2->0(4) [1.193s][info][gc,heap ] GC(1) Old regions: 0->3 [1.193s][info][gc,heap ] GC(1) Archive regions: 0->0 [1.193s][info][gc,heap ] GC(1) Humongous regions: 0->0 [1.193s][info][gc,metaspace ] GC(1) Metaspace: 6895K(7104K)->6895K(7104K) NonClass: 6323K(6400K)->6323K(6400K) Class: 571K(704K)->571K(704K) [1.193s][info][gc ] GC(1) Pause Full (WhiteBox Initiated Full GC) 1M->0M(512M) 84.846ms [1.202s][info][gc,cpu ] GC(1) User=0.19s Sys=0.63s Real=0.11s
微信搜索「個人編程喵」關注公衆號,每日一刷,輕鬆提高技術,斬獲各類offer: