為什麼 JVM 需要 GC
社群內有人發起了一個討論,關於JVM是否一定需要GC?他們認為應用程式的回收目標是構建一個僅用來處理記憶體分配,而不執行任何真正的記憶體回收操作的 GC。即僅當可用的 Java 堆耗盡的時候,才進行順序的 JVM 停頓操作。
首先需要理解為什麼需要GC。隨著應用程式所應對的業務越來越龐大、複雜,使用者越來越多,沒有GC就不能保證應用程式正常進行。而經常造成STW的GC又跟不上實際的需求,所以才會不斷地嘗試對GC進行優化。
社群的需求是儘量減少對應用程式的正常執行干擾,這也是業界目標。Oracle在JDK7時釋出G1 GC的目的是為了減少應用程式停頓發生的可能性,讓我們通過本文來了解G1 GC所做的工作。
JVM發展歷史簡介
還記得機器貓嗎?他和康夫有一張書桌,書桌的抽屜其實是一個時空穿梭通道,讓我們操作機器貓的時空機器,回到1998年。那年的12月8日,第二代Java平臺的企業版J2EE正式對外發布。為了配合企業級應用落地,1999年4月27日,Java程式的舞臺—Java HotSpot Virtual Machine(以下簡稱HotSpot )正式對外發布,並從這之後釋出的JDK1.3版本開始,HotSpot成為Sun JDK的預設虛擬機器。
GC發展歷史簡介
1999年隨JDK1.3.1一起來的是序列方式的Serial GC ,它是第一款GC,並且這只是起點。此後,JDK1.4和J2SE1.3相繼釋出。2002年2月26日,J2SE1.4釋出,Parallel GC 和Concurrent Mark Sweep (CMS)GC跟隨JDK1.4.2一起釋出,並且Parallel GC在JDK6之後成為HotSpot預設GC。
HotSpot有這麼多的垃圾回收器,那麼如果有人問,Serial GC、Parallel GC、Concurrent Mark Sweep GC這三個GC有什麼不同呢?請記住以下口令:
- 如果你想要最小化地使用記憶體和並行開銷,請選Serial GC;
- 如果你想要最大化應用程式的吞吐量,請選Parallel GC;
- 如果你想要最小化GC的中斷或停頓時間,請選CMS GC。
那麼問題來了,既然我們已經有了上面三個強大的GC,為什麼還要釋出Garbage First(G1)GC?原因就在於應用程式所應對的業務越來越龐大、複雜,使用者越來越多,沒有GC就不能保證應用程式正常進行,而經常造成STW的GC又跟不上實際的需求,所以才會不斷地嘗試對GC進行優化。
為什麼名字叫做Garbage First(G1)呢?
因為G1是一個並行回收器,它把堆記憶體分割為很多不相關的區間(Region),每個區間可以屬於老年代或者年輕代,並且每個年齡代區間可以是物理上不連續的。
老年代區間這個設計理念本身是為了服務於並行後臺執行緒,這些執行緒的主要工作是尋找未被引用的物件。而這樣就會產生一種現象,即某些區間的垃圾(未被引用物件)多於其他的區間。
垃圾回收時實則都是需要停下應用程式的,不然就沒有辦法防治應用程式的干擾 ,然後G1 GC可以集中精力在垃圾最多的區間上,並且只會費一點點時間就可以清空這些區間裡的垃圾,騰出完全空閒的區間。
繞來繞去終於明白了,由於這種方式的側重點在於處理垃圾最多的區間,所以我們給G1一個名字:垃圾優先(Garbage First)。
G1 GC基本思想
G1 GC是一個壓縮收集器,它基於回收最大量的垃圾原理進行設計。G1 GC利用遞增、並行、獨佔暫停這些屬性,通過拷貝方式完成壓縮目標。此外,它也藉助並行、多階段並行標記這些方式來幫助減少標記、重標記、清除暫停的停頓時間,讓停頓時間最小化是它的設計目標之一。
G1回收器是在JDK1.7中正式投入使用的全新的垃圾回收器,從長期目標來看,它是為了取代CMS 回收器。G1回收器擁有獨特的垃圾回收策略,這和之前提到的回收器截然不同。從分代上看,G1依然屬於分代型垃圾回收器,它會區分年輕代和老年代,年輕代依然有Eden區和Survivor區,但從堆的結構上看,它並不要求整個Eden區、年輕代或者老年代在物理上都是連續。
綜合來說,G1使用了全新的分割槽演算法,其特點如下所示:
- 並行性:G1在回收期間,可以有多個GC執行緒同時工作,有效利用多核計算能力;
- 併發性:G1擁有與應用程式交替執行的能力,部分工作可以和應用程式同時執行,因此,一般來說,不會在整個回收階段發生完全阻塞應用程式的情況;
- 分代GC:G1依然是一個分代收集器,但是和之前的各類回收器不同,它同時兼顧年輕代和老年代。對比其他回收器,或者工作在年輕代,或者工作在老年代;
- 空間整理:G1在回收過程中,會進行適當的物件移動,不像CMS只是簡單地標記清理物件。在若干次GC後,CMS必須進行一次碎片整理。而G1不同,它每次回收都會有效地複製物件,減少空間碎片,進而提升內部迴圈速度。
- 可預見性:由於分割槽的原因,G1可以只選取部分割槽域進行記憶體回收,這樣縮小了回收的範圍,因此對於全域性停頓情況的發生也能得到較好的控制。
隨著G1 GC的出現,GC從傳統的連續堆記憶體佈局設計,逐漸走向不連續記憶體塊,這是通過引入Region概念實現,也就是說,由一堆不連續的Region組成了堆記憶體。其實也不能說是不連續的,只是它從傳統的物理連續逐漸改變為邏輯上的連續,這是通過Region的動態分配方式實現的,我們可以把一個Region分配給Eden、Survivor、老年代、大物件區間、空閒區間等的任意一個,而不是固定它的作用,因為越是固定,越是呆板。
G1 GC垃圾回收機制
通過市場的力量,不斷淘汰舊的行業,把有限的資源讓給那些競爭力更強、利潤率更高的企業。類似地,矽谷也在不斷淘汰過時的人員,從全世界吸收新鮮血液。經過半個多世紀的發展,在矽谷地區便形成只有卓越才能生存的文化。本著這樣的理念,GC承擔了淘汰垃圾、儲存優良資產的任務。
G1 GC在回收暫停階段會回收最大量的堆內區間(Region),這是它的設計目標,通過回收區間達到回收垃圾的目的。這裡只有一個例外情況,這個例外發生在並行標記階段的清除(Cleanup)步驟,如果G1 GC在清除步驟發現所有的區間都是由可回收垃圾組成的,那麼它會立即回收這些區間,並且將這些區間插入到一個基於LinkedList實現的空閒區間佇列裡,以待後用。因此,釋放這些區間並不需要等待下一個垃圾回收中斷,它是實時執行的,即清除階段起到了最後一道把控作用。這是G1 GC和之前的幾代GC的一大差別。
G1 GC的垃圾回收迴圈由三個主要型別組成:
- 年輕代迴圈
- 多步驟並行標記迴圈
- 混合收集迴圈
- Full GC
在年輕代回收期,G1 GC暫停應用程式執行緒,然後從年輕代區間移動存活物件到Survivor區間或者老年區間,也有可能是兩個區間都會涉及。對於一個混合回收期,G1 GC從老年區間移動存活物件到空閒區間,這些空閒區間也就成為了老年代的一部分。
G1的區間設計靈感
為了加快GC的回收速度,HotSpot的歷代GC都有自己的不同的設計方案,區間概念在軟體設計、架構領域並不是一個新名詞,關係型資料庫、列式資料庫最先使用這個概念提升資料存、取速度,軟體架構設計時也廣泛使用這樣的分割槽概念加快資料交換、計算。
為什麼會有區間這個設計想法?大家一定看過電視劇《大宅門》吧?大宅門所描述的北京知名醫術世家白家是這本電視劇的主角。白家有三兄弟,沒有分家之前,由老爺子一手掌管全家,老爺子看似是個精明人,實質是個糊塗的人,否則也不會弄得後來白家家破人散。白家的三兄弟在沒有分家之前,老大一家很老實,老二很懦弱,性格像女人,雖然肚子裡明白道理,但是不敢出來做主。老三年輕時混蛋一個,每次出外採購藥材都要私吞家裡的銀兩,造成賬目混亂。老大為了家庭和睦,一直在私下倒貼銀兩,讓老爺子能夠看到一本正常的賬目。這樣的一家子聚在一起,遲早家庭內部會出現問題,倒不如分家,你也不用算計家裡的錢了,分給你,分給你的錢有本事守住,沒本事就一直拮据下去吧。這就是最原始的分割槽(Region)概念。
我們回到技術,看看HBase的RegionServer設計方式。在HBase內部,所有的使用者資料以及後設資料的請求,在經過Region的定位,最終會落在RegionServer上,並由RegionServer實現資料的讀寫操作。RegionServer是HBase叢集執行在每個工作節點上的服務。它是整個HBase系統的關鍵所在,一方面它維護了Region的狀態,提供了對於Region的管理和服務;另一方面,它與Master互動,上傳Region的負載資訊上傳,參與Master的分散式協調管理。
HRegionServer與HMaster以及Client之間採用RPC協議進行通訊。HRegionServer向HMaster定期彙報節點的負載狀況,包括RS記憶體使用狀態、線上狀態的Region等資訊。在該過程中HRegionServer扮演了RPC客戶端的角色,而HMaster扮演了RPC伺服器端的角色。HRegionServer內建的RpcServer實現了資料更新、讀取、刪除的操作,以及Region涉及到Flush、Compaction、Open、Close、Load檔案等功能性操作。
Region是HBase資料儲存和管理的基本單位。HBase使用RowKey將表水平切割成多個HRegion,從HMaster的角度,每個HRegion都紀錄了它的StartKey和EndKey(第一個HRegion的StartKey為空,最後一個HRegion的EndKey為空),由於RowKey是排序的,因而Client可以通過HMaster快速的定位每個RowKey在哪個HRegion中。HRegion由HMaster分配到相應的HRegionServer中,然後由HRegionServer負責HRegion的啟動和管理,和Client的通訊,負責資料的讀(使用HDFS)。每個HRegionServer可以同時管理1000個左右的HRegion。
再來看看軟體系統架構方面的分割槽設計。以任務排程為例,假設我們有一箇中心排程服務,那麼當資料量不斷增多,這個中心排程服務一定會遇到效能瓶頸,因為所有的請求都會最終指向它。為了解決這個效能瓶頸,我們可以將任務排程拆分為多個服務,即這多個服務都可以處理任務排程工作,那麼問題來了,每個任務排程服務處理的源資料是否需要完全一致?
根據華為公司釋出的專利發明,顯示他們對於每一個任務排程服務有資料來源區分的操作,即按照任務排程數量對源資料進行劃分,比如3個任務排程服務,那麼源資料按照行號對3取餘的方式劃分,如果執行了一段時間之後,任務排程服務出現了數量上的增減,那麼這個取餘劃分需要重新進行,要按照那個時候的任務排程數量重新劃分割槽間。
回到G1。在G1中,堆被平均分成若干個大小相等的區域(Region)。每個Region都有一個關聯的Remembered Set(簡稱RS),RS的資料結構是Hash表,裡面的資料是Card Table (堆中每512byte對映在card table 1byte)。
簡單的說RS裡面存在的是Region中存活物件的指標。當Region中資料發生變化時,首先反映到Card Table中的一個或多個Card上,RS通過掃描內部的Card Table得知Region中記憶體使用情況和存活物件。在使用Region過程中,如果Region被填滿了,分配記憶體的執行緒會重新選擇一個新的Region,空閒Region被組織到一個基於連結串列的資料結構(LinkedList)裡面,這樣可以快速找到新的Region。
總結
沒有GC機制的JVM是不能想象的,我們只能通過不斷優化它的使用、不斷調整自己的應用程式,避免出現大量垃圾,而不是一味認為GC造成了應用程式問題。
作者介紹
周明耀,2004年畢業於浙江大學,工學碩士,國外投資銀行12年工作經驗,4年分散式系統、物聯網工作經驗,10年技術團隊管理經驗。IBM開發者論壇專家作者、InfoQ專欄作者,著有《大話Java效能優化》、《深入理解JVM&G1 GC》,提交分散式計算領域發明專利超過15項。
相關文章
- GC是什麼?為什麼要有GC?GC
- 2021-2-26:為什麼需要 System.gc() ?GC
- 為什麼Go不再需要Java風格的GC?- itnextGoJavaGC
- JVM 從入門到實戰 --- 02 什麼樣的物件需要被 GCJVM物件GC
- 什麼是垃圾蒐集(GC)?為什麼要有GC呢?GC
- 有了HotSpot JVM為什麼還需要OpenJ9?HotSpotJVM
- 深入理解JVM——(三)為什麼JVM新生代需要兩個Survivor區JVM
- GC是什麼?為什麼我們要去使用它GC
- 為什麼需要Docker?Docker
- 為什麼需要拆分NFT?
- 為什麼GC(垃圾回收)必須stop-the-world?GC
- Elasticsearch:是什麼?你為什麼需要他?Elasticsearch
- JVM 系列文章之 Full GC 和 Minor GCJVMGC
- GC 為什麼要掛起使用者執行緒? 什麼愁什麼怨?GC執行緒
- JVM的GC日誌JVMGC
- JVM GC日誌解析JVMGC
- JVM之GC趣解JVMGC
- 為什麼我們需要 VuexVue
- 為什麼需要資料治理
- 為什麼需要依賴注入依賴注入
- 爬蟲為什麼需要HTTP?爬蟲HTTP
- 為什麼需要require.jsUIJS
- 為什麼企業需要Kaizen?AI
- 我們為什麼需要CDP?
- 為什麼MCU也需要AI?AI
- 【前端筆記】Vuex 是什麼,為什麼需要前端筆記Vue
- 什麼是 SCRM,企業為什麼需要SCRM?
- java中什麼樣的物件才能作為gc root物件,gc root物件有哪些Java物件GC
- JVM GC 日誌詳解JVMGC
- JVM記憶體-GC策略JVM記憶體GC
- JVM(六)——GC 演算法JVMGC演算法
- JVM 虛擬機器 GCJVM虛擬機GC
- JVM+GC 面試題JVMGC面試題
- 聊一聊 JVM 的 GCJVMGC
- 邊緣計算是什麼以及為什麼需要它
- 什麼是Web workers?為什麼我們需要他Web
- 我們為什麼需要async/await ?AI
- 為什麼需要Deno.js? - DEVJSdev
- 到底為什麼我們需要 Clickhouse?