
彭成寒
G1是目前最成熟的垃圾回收器,已經廣泛應用在衆多公司的生產環境中。我們知道,CMS作爲使用最爲廣泛的垃圾回收器,也有令人頭疼的問題,即如何對其衆多的參數進行正確的設置。G1的目標就是替代CMS,所以在設計之初就希望降低程序員的負擔,減少人工的介入。但這並不意味着我們完全不需要了解G1的原理和參數調優。筆者在實際工作中遇到過一些因參數設置不正確而導致GC停頓時間過長的問題。但要正確設置參數並不容易,這裏涉及兩個方面:第一,需要對G1的原理熟悉,只有熟悉G1的原理才知道調優的方向;第二,能分析和解讀G1運行的日誌信息,根據日誌信息找到G1運行過程中的異常信息,並推斷哪些參數可以解決這些異常。本書嘗試從G1的原理出發,系統地介紹新生代回收、混合回收、Full GC、併發標記、Refine線程等內容;同時依託於jdk8u的源代碼介紹Hotspot如何實現G1,通過對源代碼的分析來了解G1提供了哪些參數、這些參數的具體意義;最後本書還設計了一些示例代碼,給出了G1在運行這些示例代碼時的日誌,通過日誌分析來嘗試調整參數並達到性能優化,還分析了參數調整可能帶來的負面影響。
1、垃圾標記的算法: (1)垃圾回收分成了垃圾標記和垃圾清理 垃圾標記分成了 【可達性分析】、【引用計數】 兩種方式 (2)python還在使用引用計數,這裏有判斷邏輯,如果出現循環引用,使用可達性分析兜底。 補充:python這個還是別想了,引用技術和跨代引用之間有很多問題並不能解釋清楚。 2、垃圾清理算法: (1)【標記清除】、【標記整理】、【複製算法】;G1使用的完全是複製算法。 (2)G1的內存組成,先分Young和Old,Young裏面再分Eden和Survivor區分Young和Old的原因,是對象的生命週期差距很大,區分開之後可以減少無效掃描;Eden和Survivor是複製算法本來就需要至少兩塊,Survivor搞小一點是避免浪費,所以有下面的8:1:1。 (3)數據大小佔比 複製算法涉及到Eden和Survivor來回倒,默認比例8:1:1,這個會動態計算。 新生代Young開局佔總體內存的1/3,也是動態的,老年代佔據的地盤更大;老年代默認給新生代保留10%的晉升空間。 補充:Young、Old比例這個完全沒準,common-api的yong佔了30%,api-service就能佔50%,就看新對象垃圾多少了。 (4)CMS的標記整理算法 併發線程數通常會喫掉25%的CPU資源,它沒有記憶集,全是CPU現場計算的。 3、記憶集RSet的維護 (1)【分區】,region,默認是2048個,用堆大小除以2048,每個region大小往2的冪次上去靠。按照8G內存來算,就是2M。 這個大小唯一的影響是超過region一半的對象,會直接進入老年代;region如果比較小,可以對內存進行精細化管理,但也會增加RSet管理的開銷。 (2)【卡表】,card,默認是512字節。這個卡表只用來標識哪個區域髒了,每當有引用變更,G1會通過寫屏障把卡表標記爲髒卡。 後臺會有一個Refine線程持續在刷這個髒卡,把它們掃一遍更新到RSet裏。 【卡表這裏終於看懂了】卡表的card是對region的極細粒度的細分,這個大小基本就是對象級別。當某個對象引用發生變化的時候,把card放到髒隊列裏面,基本等同於把對象放到髒隊列裏面!而記憶集不使用card也是因爲card粒度過細,存儲浪費成本太多了。 (3)【Dirty Card Queue隊列】的維護,每次變更的時候,線程都先把這個變更消息存放在線程本地,超過一定數值之後再上報,當然也有超時時間兜底。 (4)【記憶集RSet】,分成了三種類型,按照Region爲單位的 a.第一種類型是當前Region被外部引用的太少了,每次多一個外部引用都能精確記錄到卡表指針,稀疏表。 b.第二種類型是數量多一點之後,每個card維護一個位圖,把一個region分成512份,來表示對面對自己card的引用。 c.第三種類型是再多了,只能維護region對region的位圖。 除了新生代對新生代自己之外,其他跨代引用和老年代對老年代引用都要記錄。 4、【Young年輕代空間大小的推算】--G1的核心 年輕代空間大小的推算,受控於【兩個時間】:G1的目的就是停頓時間可控 (1)【Eden的STW停頓時間】,這有一個上限 據不完全統計,每有1G的Eden,停頓時間就會上漲100ms,所以停頓時間和內存大小基本是相互轉化的 (2)【G1線程總體佔用的時間】,併發時間也算在裏面,都是喫CPU時間片的 如果Eden太小了,GC就頻繁,總體G1線程的時間就多,這個默認不能超過10%。 如果Eden大了,STW時間就多了,這個默認200ms。Eden每有1G通常就需要100ms。 (3)Young佔總內存有一個上限的,這個上限默認是60%,老年代總得留點(其實根本到不了這個上限) 5、Eden清理的過程: (1)【STW】,使用CSet作爲根集合進行掃描 (2)【Eden掃描和複製】是一體的,一旦發現對象存活,直接拷貝,內存完全copy一份不需要解析。 複製完之後老對象的對象頭要做標記,指向新對象。 (3)【ReDirty】以及【重構卡表RSet】複製完之後,把Eden和指向Eden的老年代再掃描一遍,這一遍要把持有引用的那些對象,持有的引用變更一遍; 這時候Eden的內容已經都被複制到Survivor裏去了,所以需要掃描的就是指向Survivor裏面的這些對象,把引用變更到新對象上去。需要掃描的是之前的老年代RSet以及當前的Survivor。掃完一遍之後,引用變更了,RSet也重構出來了。(4)釋放剛纔回收的頁面到【FreeList】,供下次申請內存用 6、快速分配和慢速分配 (1)TLAB佔Eden的1%。每個線程創建的時候分配兩段TLAB,本地內存,分配的時候先從這裏出。 分配兩段是因爲有GC,GC的時候原來那段就不能用了,得分配到新空間裏。 (2)【慢速分配就是嘗試申請一段新的TLAB】;JVM分配光了就觸發GC。 7、完整垃圾回收過程: (1)新生代回收 STW,深度遞歸遍歷有存活對象就拷貝,拷貝完之後再遍歷重構RSet和持有引用對象的引用值 (2)混合回收或者老年代回收 a. 混合回收默認在YGC之後內存還超過45%的時候開啓 b. 初始標記,併發標記,再標記,併發清除 (3)三色標記詳細過程 三色標記是併發標記過程中使用的算法,初始標記、併發標記、再標記三個階段就是爲了生產出一張【標記位圖】來,用0101標識出哪個對象是垃圾,哪個是存活對象。 三色標記裏有一類新對象和兩類變更對象: a. 【新分配的對象利用TLAB的開始位置start做快照】,所有的對象都是在TLAB上分配的,TLAB不夠會再申請。 b. 變更對象是在併發標記過程中,如果變化了就把對象扔到SATB隊列中等待重新處理。
最好的GC就是讓大家不需要關心GC,希望未來的Java不需要再讀此類的書籍了。
代碼的篇幅過大,非常不利於手機閱讀。多一點概念和原理上的闡述比較好,書中不適合穿插大量代碼,,畢竟書本中的代碼不能像在IDE中一樣有語法高亮,閱讀不效率,太多了,看的累,極度影響閱讀體驗!
前面描述的看到還可以,也幫我解決了Java線程與OS線程如何關聯起來的,後面的實在有很多東西嗎接觸到,完全不知所云[大哭],就很快速的看完,希望以後再看時,能好點[捂臉]
有實際優化經驗來看這本書要好得多。
對讀者的水平要求還是比較高。 作者的表達有時候確實有點問題,一些生僻的、易混淆的概念本可以歸納下的,但是文章整體結構的問題,讀起來就讓人覺得很費勁。比如在介紹mixed gc的各個流程的時候,併發標記、併發標記子流程這兩個概念,在行文中有好幾處混淆。 但總的說來,這是一本功底深厚的書。從基本原理(不僅僅只是G1中涉及的原理),到源碼,以至於基本的優化方向、優化方法,作者都廣泛涉獵、融匯貫通。 受益匪淺。
現在大部分公司都已經是jdk8了,並且在jdk11默認已經是G1,在互聯網,業務熟練擴張的情況下需要高響應,低延遲的場景也越來越多,並且每一代gc回收器的目標都是爲了減少stw.這個書對G1還是很全面的。
不知道作者心中的目標讀者是什麼樣的。原理講的不清楚,只是在堆砌細節,讓人不知所云。
文字功底較差,文章組織講解條理性不是很清晰
行文邏輯散亂,更像是作者自己的筆記