2021-2-26:為什麼需要 System.gc() ?

乾貨滿滿張雜湊發表於2021-02-26

JVM 預設啟動引數中,DisableExplicitGC 為 false,ExplicitGCInvokesConcurrent 為 false,對於大多數 GC (除了 ZGC 的其他 GC,包括 CMS,G1,Shenandoah GC 等等),都是會進行 FullGC 的,並且都是同步 GC 的,其中底層的原理會在另一篇詳細分析,我們先來搞清楚為什麼要留這樣一個介面呢?

1. 使用並管理堆外記憶體的框架,需要 Full GC 的機制觸發堆外記憶體回收

JVM 的記憶體,不止堆記憶體,還有其他很多塊,通過 Native Memory Tracking 可以看到:

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) 

  • Java Heap: 堆記憶體,即-Xmx限制的最大堆大小的記憶體。
  • Class:載入的類與方法資訊,其實就是 metaspace,包含兩部分: 一是 metadata,被-XX:MaxMetaspaceSize限制最大大小,另外是 class space,被-XX:CompressedClassSpaceSize限制最大大小
  • Thread:執行緒與執行緒棧佔用記憶體,每個執行緒棧佔用大小受-Xss限制,但是總大小沒有限制。
  • Code:JIT 即時編譯後(C1 C2 編譯器優化)的程式碼佔用記憶體,受 -XX:ReservedCodeCacheSize限制
  • GC:垃圾回收佔用記憶體,例如垃圾回收需要的 CardTable,標記數,區域劃分記錄,還有標記 GC Root 等等,都需要記憶體。這個不受限制,一般不會很大的。
  • Compiler:C1 C2 編譯器本身的程式碼和標記佔用的記憶體,這個不受限制,一般不會很大的
  • Internal:命令列解析,JVMTI 使用的記憶體,這個不受限制,一般不會很大的
  • Symbol: 常量池佔用的大小,字串常量池受-XX:StringTableSize 個數限制,總記憶體大小不受限制
  • Native Memory Tracking:記憶體採集本身佔用的記憶體大小,如果沒有開啟採集(那就看不到這個了,哈哈),就不會佔用,這個不受限制,一般不會很大的
  • Arena Chunk:所有通過 arena 方式分配的記憶體,這個不受限制,一般不會很大的
  • Tracing:所有采集佔用的記憶體,如果開啟了 JFR 則主要是 JFR 佔用的記憶體。這個不受限制,一般不會很大的
  • Logging,Arguments,Module,Synchronizer,Safepoint,Other,這些一般我們不會關心。

除了 Native Memory Tracking 記錄的記憶體使用,還有兩種記憶體 Native Memory Tracking 沒有記錄,那就是:

  • Direct Buffer:直接記憶體
  • MMap Buffer:檔案對映記憶體

針對除了堆記憶體以外,其他的記憶體,有些也是需要 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.

2. 使用了 WeakReference, SoftReference 的程式,需要相應的 GC 回收。

對於 WeakReference,只要發生 GC,無論是 Young GC 還是 FullGC 就會被回收。SoftReference 只有在 FullGC 的時候才會被回收。當我們程式想主動對於這些引用進行回收的時候,需要能觸發 GC 的方法,這就用到了System.gc()

3. 測試,學習 JVM 機制的時候

有些時候,我們為了測試,學習 JVM 的某些機制,需要讓 JVM 做一次 GC 之後開始,這也會用到System.gc()

微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer

image

相關文章