search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

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項。

手機QQ春節紅包項目組的復盤總結

推薦一個對技術人員的成長很有幫助的線下會議,將於4月16~18日舉行的QCon全球軟體開發大會(北京站),目前已經邀請來自Google、Facebook、LinkedIn、Airbnb、百度、阿里巴巴、騰訊等公司的100多位一線技術專家,是難得的線下交流學習的機會。具體詳戳「 閱讀原文 」驚喜不停!



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦