簡單理解: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的默認虛擬機。性能優化

clipboard.png

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使用了全新的分區算法,其特色以下所示:

  1. 並行性:G1在回收期間,能夠有多個GC線程同時工做,有效利用多核計算能力;
  2. 併發性:G1擁有與應用程序交替執行的能力,部分工做能夠和應用程序同時執行,所以,通常來講,不會在整個回收階段發生徹底阻塞應用程序的狀況;
  3. 分代GC:G1依然是一個分代收集器,可是和以前的各種回收器不一樣,它同時兼顧年輕代和老年代。對比其餘回收器,或者工做在年輕代,或者工做在老年代;
  4. 空間整理:G1在回收過程當中,會進行適當的對象移動,不像CMS只是簡單地標記清理對象。在若干次GC後,CMS必須進行一次碎片整理。而G1不一樣,它每次回收都會有效地複製對象,減小空間碎片,進而提高內部循環速度。
  5. 可預見性:因爲分區的緣由,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的分佈式協調管理。

clipboard.png

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

clipboard.png

簡單的說RS裏面存在的是Region中存活對象的指針。當Region中數據發生變化時,首先反映到Card Table中的一個或多個Card上,RS經過掃描內部的Card Table得知Region中內存使用狀況和存活對象。在使用Region過程當中,若是Region被填滿了,分配內存的線程會從新選擇一個新的Region,空閒Region被組織到一個基於鏈表的數據結構(LinkedList)裏面,這樣能夠快速找到新的Region。

clipboard.png

總結

沒有GC機制的JVM是不能想象的,咱們只能經過不斷優化它的使用、不斷調整本身的應用程序,避免出現大量垃圾,而不是一味認爲GC形成了應用程序問題。

在此我向你們推薦一個架構學習交流羣。交流學習羣號:478030634 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多

clipboard.png

你們以爲文章對你仍是有一點點幫助的,你們能夠點擊下方二維碼進行關注。 《Java爛豬皮》 公衆號聊的不只僅是Java技術知識,還有面試等乾貨,後期還有大量架構乾貨。你們一塊兒關注吧!關注爛豬皮,你會了解的更多..............

相關文章
相關標籤/搜索