爲什麼JVM需要 "垃圾回收器" ?

  本文出自:http://www.hello-code.com

  首先需要理解爲什麼需要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的垃圾回收循環由三個主要類型組成:

1.年輕代循環 
2.多步驟並行標記循環 
3.混合收集循環 
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造成了應用程序問
題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章