為什么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造成了應用程序問
題。

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com