有哪些關(guān)于JVM問題

本篇內(nèi)容主要講解“有哪些關(guān)于JVM問題”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“有哪些關(guān)于JVM問題”吧!

創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),秦都企業(yè)網(wǎng)站建設(shè),秦都品牌網(wǎng)站建設(shè),網(wǎng)站定制,秦都網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,秦都網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。

young gc、old gc、full gc、mixed gc 傻傻分不清?

這個問題的前置條件是你得知道 GC 分代,為什么分代。這個在之前文章提了,不清楚的可以去看看。

現(xiàn)在我們來回答一下這個問題。

其實 GC 分為兩大類,分別是 Partial GC 和 Full GC。

Partial GC 即部分收集,分為 young gc、old gc、mixed gc。

  • young gc:指的是單單收集年輕代的 GC。

  • old gc:指的是單單收集老年代的 GC。

  • mixed gc:這個是 G1 收集器特有的,指的是收集整個年輕代和部分老年代的 GC。

Full GC 即整堆回收,指的是收取整個堆,包括年輕代、老年代,如果有永久代的話還包括永久代。

其實還有 Major GC 這個名詞,在《深入理解Java虛擬機》中這個名詞指代的是單單老年代的 GC,也就是和 old gc 等價的,不過也有很多資料認為其是和 full gc 等價的。

還有 Minor GC,其指的就是年輕代的 gc。

young gc 觸發(fā)條件是什么?

大致上可以認為在年輕代的 eden 快要被占滿的時候會觸發(fā) young gc。

為什么要說大致上呢?因為有一些收集器的回收實現(xiàn)是在 full gc 前會讓先執(zhí)行以下 young gc。

比如 Parallel Scavenge,不過有參數(shù)可以調(diào)整讓其不進行 young gc。

可能還有別的實現(xiàn)也有這種操作,不過正常情況下就當(dāng)做 eden 區(qū)快滿了即可。

eden 快滿的觸發(fā)因素有兩個,一個是為對象分配內(nèi)存不夠,一個是為 TLAB 分配內(nèi)存不夠。

full gc 觸發(fā)條件有哪些?

這個觸發(fā)條件稍微有點多,我們來看下。

  • 在要進行 young gc 的時候,根據(jù)之前統(tǒng)計數(shù)據(jù)發(fā)現(xiàn)年輕代平均晉升大小比現(xiàn)在老年代剩余空間要大,那就會觸發(fā) full gc。

  • 有永久代的話如果永久代滿了也會觸發(fā) full gc。

  • 老年代空間不足,大對象直接在老年代申請分配,如果此時老年代空間不足則會觸發(fā) full gc。

  • 擔(dān)保失敗即 promotion failure,新生代的 to 區(qū)放不下從 eden 和 from 拷貝過來對象,或者新生代對象 gc 年齡到達閾值需要晉升這兩種情況,老年代如果放不下的話都會觸發(fā) full gc。

  • 執(zhí)行 System.gc()、jmap -dump 等命令會觸發(fā) full gc。

知道 TLAB 嗎?來說說看

這個得從內(nèi)存申請說起。

一般而言生成對象需要向堆中的新生代申請內(nèi)存空間,而堆又是全局共享的,像新生代內(nèi)存又是規(guī)整的,是通過一個指針來劃分的。

有哪些關(guān)于JVM問題

內(nèi)存是緊湊的,新對象創(chuàng)建指針就右移對象大小 size 即可,這叫指針加法(bump [up] the pointer)。

可想而知如果多個線程都在分配對象,那么這個指針就會成為熱點資源,需要互斥那分配的效率就低了。

于是搞了個 TLAB(Thread Local Allocation Buffer),為一個線程分配的內(nèi)存申請區(qū)域。

這個區(qū)域只允許這一個線程申請分配對象,允許所有線程訪問這塊內(nèi)存區(qū)域。

TLAB 的思想其實很簡單,就是劃一塊區(qū)域給一個線程,這樣每個線程只需要在自己的那畝地申請對象內(nèi)存,不需要爭搶熱點指針。

當(dāng)這塊內(nèi)存用完了之后再去申請即可。

這種思想其實很常見,比如分布式發(fā)號器,每次不會一個一個號的取,會取一批號,用完之后再去申請一批。

有哪些關(guān)于JVM問題

可以看到每個線程有自己的一塊內(nèi)存分配區(qū)域,短一點的箭頭代表 TLAB 內(nèi)部的分配指針。

如果這塊區(qū)域用完了再去申請即可。

不過每次申請的大小不固定,會根據(jù)該線程啟動到現(xiàn)在的歷史信息來調(diào)整,比如這個線程一直在分配內(nèi)存那么 TLAB 就大一些,如果這個線程基本上不會申請分配內(nèi)存那 TLAB 就小一些。

還有 TLAB 會浪費空間,我們來看下這個圖。

有哪些關(guān)于JVM問題

可以看到 TLAB 內(nèi)部只剩一格大小,申請的對象需要兩格,這時候需要再申請一塊 TLAB ,之前的那一格就浪費了。

在 HotSpot 中會生成一個填充對象來填滿這一塊,因為堆需要線性遍歷,遍歷的流程是通過對象頭得知對象的大小,然后跳過這個大小就能找到下一個對象,所以不能有空洞。

當(dāng)然也可以通過空閑鏈表等外部記錄方式來實現(xiàn)遍歷。

還有 TLAB 只能分配小對象,大的對象還是需要在共享的 eden 區(qū)分配

所以總的來說 TLAB 是為了避免對象分配時的競爭而設(shè)計的。

那 PLAB 知道嗎?

可以看到和 TLAB 很像,PLAB 即 Promotion Local Allocation Buffers。

用在年輕代對象晉升到老年代時。

在多線程并行執(zhí)行 YGC 時,可能有很多對象需要晉升到老年代,此時老年代的指針就“熱”起來了,于是搞了個 PLAB。

先從老年代 freelist(空閑鏈表) 申請一塊空間,然后在這一塊空間中就可以通過指針加法(bump the pointer)來分配內(nèi)存,這樣對 freelist 競爭也少了,分配空間也快了。

有哪些關(guān)于JVM問題

大致就是上圖這么個思想,每個線程先申請一塊作為 PLAB ,然后在這一塊內(nèi)存里面分配晉升的對象。

這和 TLAB 的思想相似。

產(chǎn)生 concurrent mode failure 真正的原因

《深入理解Java虛擬機》:由于CMS收集器無法處理“浮動垃圾”(FloatingGarbage),有可能出現(xiàn)“Con-current Mode Failure”失敗進而導(dǎo)致另一次完全“Stop The World”的Full GC的產(chǎn)生。

這段話的意思是因為拋這個錯而導(dǎo)致一次 Full GC。

實際上是 Full GC 導(dǎo)致拋這個錯,我們來看一下源碼,版本是 openjdk-8。

首先搜一下這個錯。

有哪些關(guān)于JVM問題

再找找看 report_concurrent_mode_interruption 被誰調(diào)用。

查到是在 void CMSCollector::acquire_control_and_collect(...) 這個方法中被調(diào)用的。

有哪些關(guān)于JVM問題

再來看看 first_state : CollectorState first_state = _collectorState;

有哪些關(guān)于JVM問題

看枚舉已經(jīng)很清楚了,就是在 cms gc 還沒結(jié)束的時候。

acquire_control_and_collect 這個方法是 cms 執(zhí)行 foreground gc 的。

cms 分為 foreground gc 和 background gc。

foreground 其實就是 Full gc。

因此是 full gc 的時候 cms gc 還在進行中導(dǎo)致拋這個錯。

究其原因是因為分配速率太快導(dǎo)致堆不夠用,回收不過來因此產(chǎn)生 full gc。

也有可能是發(fā)起 cms gc 設(shè)置的堆的閾值太高。

CMS GC 發(fā)生 concurrent mode failure 時的 full GC 為什么是單線程的?

以下的回答來自 R 大

因為沒足夠開發(fā)資源,偷懶了。就這么簡單。沒有任何技術(shù)上的問題。 大公司都自己內(nèi)部做了優(yōu)化。

所以最初怎么會偷這個懶的呢?多災(zāi)多難的CMS GC經(jīng)歷了多次動蕩。它最初是作為Sun Labs的Exact VM的低延遲GC而設(shè)計實現(xiàn)的。

但 Exact VM在與 HotSpot VM爭搶 Sun 的正牌 JVM 的內(nèi)部斗爭中失利,CMS GC 后來就作為 Exact VM 的技術(shù)遺產(chǎn)被移植到了 HotSpot VM上。

就在這個移植還在進行中的時候,Sun 已經(jīng)開始略顯疲態(tài);到 CMS GC 完全移植到 HotSpot VM 的時候,Sun 已經(jīng)處于快要不行的階段了。

開發(fā)資源減少,開發(fā)人員流失,當(dāng)時的 HotSpot VM 開發(fā)組能夠做的事情并不多,只能挑重要的來做。而這個時候 Sun Labs 的另一個 GC 實現(xiàn),Garbage-First GC(G1 GC)已經(jīng)面世。

相比可能在長時間運行后受碎片化影響的 CMS,G1 會增量式的整理/壓縮堆里的數(shù)據(jù),避免受碎片化影響,因而被認為更具潛力。

于是當(dāng)時本來就不多的開發(fā)資源,一部分還投給了把G1 GC產(chǎn)品化的項目上——結(jié)果也是進展緩慢。

畢竟只有一兩個人在做。所以當(dāng)時就沒能有足夠開發(fā)資源去打磨 CMS GC 的各種配套設(shè)施的細節(jié),配套的備份 full GC 的并行化也就耽擱了下來。

但肯定會有同學(xué)抱有疑問:HotSpot VM不是已經(jīng)有并行GC了么?而且還有好幾個?

讓我們來看看:

  • ParNew:并行的young gen GC,不負責(zé)收集old gen。

  • Parallel GC(ParallelScavenge):并行的young gen GC,與ParNew相似但不兼容;同樣不負責(zé)收集old gen。

  • ParallelOld GC(PSCompact):并行的full GC,但與ParNew / CMS不兼容。

所以…就是這么一回事。

HotSpot VM 確實是已經(jīng)有并行 GC 了,但兩個是只負責(zé)在 young GC 時收集 young gen 的,這倆之中還只有 ParNew 能跟 CMS 搭配使用;

而并行 full GC 雖然有一個 ParallelOld,但卻與 CMS GC 不兼容所以無法作為它的備份 full GC使用。

為什么有些新老年代的收集器不能組合使用比如 ParNew 和 Parallel Old?

有哪些關(guān)于JVM問題

這張圖是 2008 年 HostSpot 一位 GC 組成員畫的,那時候 G1 還沒問世,在研發(fā)中,所以畫了個問號在上面。

里面的回答是 :

"ParNew" is written in a style... "Parallel Old" is not written in the "ParNew" style

HotSpot VM 自身的分代收集器實現(xiàn)有一套框架,只有在框架內(nèi)的實現(xiàn)才能互相搭配使用。

而有個開發(fā)他不想按照這個框架實現(xiàn),自己寫了個,測試的成績還不錯后來被 HotSpot VM 給吸收了,這就導(dǎo)致了不兼容。

我之前看到一個回答解釋的很形象:就像動車組車頭帶不了綠皮車廂一樣,電氣,掛鉤啥的都不匹配。

新生代的 GC 如何避免全堆掃描?

在常見的分代 GC 中就是利用記憶集來實現(xiàn)的,記錄可能存在的老年代中有新生代的引用的對象地址,來避免全堆掃描。

有哪些關(guān)于JVM問題

上圖有個對象精度的,一個是卡精度的,卡精度的叫卡表。

把堆中分為很多塊,每塊 512 字節(jié)(卡頁),用字節(jié)數(shù)組來中的一個元素來表示某一塊,1表示臟塊,里面存在跨代引用。

有哪些關(guān)于JVM問題

在 Hotspot 中的實現(xiàn)是卡表,是通過寫后屏障維護的,偽代碼如下。

有哪些關(guān)于JVM問題

cms 中需要記錄老年代指向年輕代的引用,但是寫屏障的實現(xiàn)并沒有做任何條件的過濾。

不判斷當(dāng)前對象是老年代對象且引用的是新生代對象才會標(biāo)記對應(yīng)的卡表為臟。

只要是引用賦值都會把對象的卡標(biāo)記為臟,當(dāng)然YGC掃描的時候只會掃老年代的卡表。

這樣做是減少寫屏障帶來的消耗,畢竟引用的賦值非常的頻繁。

那 cms 的記憶集和 G1 的記憶集有什么不一樣?

cms 的記憶集的實現(xiàn)是卡表即 card table。

通常實現(xiàn)的記憶集是 points-out 的,我們知道記憶集是用來記錄非收集區(qū)域指向收集區(qū)域的跨代引用,它的主語其實是非收集區(qū)域,所以是 points-out 的。

在 cms 中只有老年代指向年輕代的卡表,用于年輕代 gc。

而 G1 是基于 region 的,所以在 points-out 的卡表之上還加了個 points-into 的結(jié)構(gòu)。

因為一個 region 需要知道有哪些別的 region 有指向自己的指針,然后還需要知道這些指針在哪些 card 中。

其實 G1 的記憶集就是個 hash table,key 就是別的 region 的起始地址,然后 value 是一個集合,里面存儲這 card table 的 index。

我們來看下這個圖就很清晰了。

有哪些關(guān)于JVM問題

像每次引用字段的賦值都需要維護記憶集開銷很大,所以 G1 的實現(xiàn)利用了 logging write barrier(下文會介紹)。

也是異步思想,會先將修改記錄到隊列中,當(dāng)隊列超過一定閾值由后臺線程取出遍歷來更新記憶集。

為什么 G1 不維護年輕代到老年代的記憶集?

G1 分了 young GC 和 mixed gc。

young gc 會選取所有年輕代的 region 進行收集。

midex gc 會選取所有年輕代的 region 和一些收集收益高的老年代 region 進行收集。

所以年輕代的 region 都在收集范圍內(nèi),所以不需要額外記錄年輕代到老年代的跨代引用

cms 和 G1 為了維持并發(fā)的正確性分別用了什么手段?

之前文章分析到了并發(fā)執(zhí)行漏標(biāo)的兩個充分必要條件是:

  1. 將新對象插入已掃描完畢的對象中,即插入黑色對象到白色對象的引用。

  2. 刪除了灰色對象到白色對象的引用。

cms 和 g1 分別通過增量更新和 SATB 來打破這兩個充分必要條件,維持了 GC 線程與應(yīng)用線程并發(fā)的正確性。

cms 用了增量更新(Incremental update),打破了第一個條件,通過寫屏障將插入的白色對象標(biāo)記成灰色,即加入到標(biāo)記棧中,在 remark 階段再掃描,防止漏標(biāo)情況。

G1 用了 SATB(snapshot-at-the-beginning),打破了第二個條件,會通過寫屏障把舊的引用關(guān)系記下來,之后再把舊引用關(guān)系再掃描過。

這個從英文名詞來看就已經(jīng)很清晰了。講白了就是在 GC 開始時候如果對象是存活的就認為其存活,等于拍了個快照。

而且 gc 過程中新分配的對象也都認為是活的。每個 region 會維持 TAMS (top at mark start)指針,分別是 prevTAMS 和 nextTAMS 分別標(biāo)記兩次并發(fā)標(biāo)記開始時候 Top 指針的位置。

Top 指針就是 region 中最新分配對象的位置,所以 nextTAMS 和 Top 之間區(qū)域的對象都是新分配的對象都認為其是存活的即可。

有哪些關(guān)于JVM問題

而利用增量更新的 cms 在 remark 階段需要重新所有線程棧和整個年輕代,因為等于之前的根有新增,所以需要重新掃描過,如果年輕代的對象很多的話會比較耗時。

要注意這階段是 STW 的,很關(guān)鍵,所以 CMS 也提供了一個 CMSScavengeBeforeRemark 參數(shù),來強制 remark 階段之前來一次 YGC。

而 g1 通過 SATB 的話在最終標(biāo)記階段只需要掃描 SATB 記錄的舊引用即可,從這方面來說會比 cms 快,但是也因為這樣浮動垃圾會比 cms 多。

什么是 logging write barrier ?

寫屏障其實耗的是應(yīng)用程序的性能,是在引用賦值的時候執(zhí)行的邏輯,這個操作非常的頻繁,因此就搞了個 logging write barrier。

把寫屏障要執(zhí)行的一些邏輯搬運到后臺線程執(zhí)行,來減輕對應(yīng)用程序的影響。

在寫屏障里只需要記錄一個 log 信息到一個隊列中,然后別的后臺線程會從隊列中取出信息來完成后續(xù)的操作,其實就是異步思想。

像 SATB write barrier ,每個 Java 線程有一個獨立的、定長的 SATBMarkQueue,在寫屏障里只把舊引用壓入該隊列中。滿了之后會加到全局 SATBMarkQueueSet。

有哪些關(guān)于JVM問題

后臺線程會掃描,如果超過一定閾值就會處理,開始 tracing。

在維護記憶集的寫屏障也用了 logging write barrier 。

簡單說下 G1 回收流程

G1 從大局上看分為兩大階段,分別是并發(fā)標(biāo)記和對象拷貝。

并發(fā)標(biāo)記是基于 STAB 的,可以分為四大階段:

1、初始標(biāo)記(initial marking),這個階段是 STW 的,掃描根集合,標(biāo)記根直接可達的對象即可。在G1中標(biāo)記對象是利用外部的bitmap來記錄,而不是對象頭。

2、并發(fā)階段(concurrent marking),這個階段和應(yīng)用線程并發(fā),從上一步標(biāo)記的根直接可達對象開始進行 tracing,遞歸掃描所有可達對象。 STAB 也會在這個階段記錄著變更的引用。

3、最終標(biāo)記(final marking), 這個階段是 STW 的,處理 STAB 中的引用。

4、清理階段(clenaup),這個階段是 STW 的,根據(jù)標(biāo)記的 bitmap 統(tǒng)計每個 region 存活對象的多少,如果有完全沒存活的 region 則整體回收。

對象拷貝階段(evacuation),這個階段是 STW 的。

根據(jù)標(biāo)記結(jié)果選擇合適的 reigon 組成收集集合(collection set 即 CSet),然后將 CSet 存活對象拷貝到新 region 中。

G1 的瓶頸在于對象拷貝階段,需要花較多的瓶頸來轉(zhuǎn)移對象。

簡單說下 cms 回收流程

其實從之前問題的 CollectorState 枚舉可以得知幾個流程了。

1、初始標(biāo)記(initial mark),這個階段是 STW 的,掃描根集合,標(biāo)記根直接可達的對象即可。

2、并發(fā)標(biāo)記(Concurrent marking),這個階段和應(yīng)用線程并發(fā),從上一步標(biāo)記的根直接可達對象開始進行 tracing,遞歸掃描所有可達對象。

3、并發(fā)預(yù)清理(Concurrent precleaning),這個階段和應(yīng)用線程并發(fā),就是想幫重新標(biāo)記階段先做點工作,掃描一下卡表臟的區(qū)域和新晉升到老年代的對象等,因為重新標(biāo)記是 STW 的,所以分擔(dān)一點。

4、可中斷的預(yù)清理階段(AbortablePreclean),這個和上一個階段基本上一致,就是為了分擔(dān)重新標(biāo)記標(biāo)記的工作。

5、重新標(biāo)記(remark),這個階段是 STW 的,因為并發(fā)階段引用關(guān)系會發(fā)生變化,所以要重新遍歷一遍新生代對象、Gc Roots、卡表等,來修正標(biāo)記。

6、并發(fā)清理(Concurrent sweeping),這個階段和應(yīng)用線程并發(fā),用于清理垃圾。

7、并發(fā)重置(Concurrent reset),這個階段和應(yīng)用線程并發(fā),重置 cms 內(nèi)部狀態(tài)。

cms 的瓶頸就在于重新標(biāo)記階段,需要較長花費時間來進行重新掃描。

cms 寫屏障又是維護卡表,又得維護增量更新?

卡表其實只有一份,又得用來支持 YGC 又得支持 CMS 并發(fā)時的增量更新肯定是不夠的。

每次 YGC 都會掃描重置卡表,這樣增量更新的記錄就被清理了。

所以還搞了個 mod-union table,在并發(fā)標(biāo)記時,如果發(fā)生 YGC 需要重置卡表的記錄時,就會更新 mod-union table 對應(yīng)的位置。

這樣 cms 重新標(biāo)記階段就能結(jié)合當(dāng)時的卡表和 mod-union table 來處理增量更新,防止漏標(biāo)對象了。

GC 調(diào)優(yōu)的兩大目標(biāo)是啥?

分別是最短暫停時間和吞吐量

最短暫停時間:因為 GC 會 STW 暫停所有應(yīng)用線程,這時候?qū)τ谟脩舳跃偷扔诳D了,因此對于時延敏感的應(yīng)用來說減少 STW 的時間是關(guān)鍵。

吞吐量:對于一些對時延不敏感的應(yīng)用比如一些后臺計算應(yīng)用來說,吞吐量是關(guān)注的重點,它們不關(guān)注每次 GC 停頓的時間,只關(guān)注總的停頓時間少,吞吐量高。

舉個例子:

方案一:每次 GC 停頓 100 ms,每秒停頓 5 次。

方案二:每次 GC 停頓 200 ms,每秒停頓 2 次。

兩個方案相對而言第一個時延低,第二個吞吐高,基本上兩者不可兼得。

所以調(diào)優(yōu)時候需要明確應(yīng)用的目標(biāo)

GC 如何調(diào)優(yōu)

這個問題在面試中很容易問到,抓住核心回答。

現(xiàn)在都是分代 GC,調(diào)優(yōu)的思路就是盡量讓對象在新生代就被回收,防止過多的對象晉升到老年代,減少大對象的分配。

需要平衡分代的大小、垃圾回收的次數(shù)和停頓時間。

需要對 GC 進行完整的監(jiān)控,監(jiān)控各年代占用大小、YGC 觸發(fā)頻率、Full GC 觸發(fā)頻率,對象分配速率等等。

然后根據(jù)實際情況進行調(diào)優(yōu)。

比如進行了莫名其妙的 Full GC,有可能是某個第三方庫調(diào)了 System.gc。

Full GC 頻繁可能是 CMS GC 觸發(fā)內(nèi)存閾值過低,導(dǎo)致對象分配不過來。

還有對象年齡晉升的閾值、survivor 過小等等,具體情況還是得具體分析,反正核心是不變的。

到此,相信大家對“有哪些關(guān)于JVM問題”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

新聞名稱:有哪些關(guān)于JVM問題
當(dāng)前地址:http://muchs.cn/article4/jpedie.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)網(wǎng)站建設(shè)、商城網(wǎng)站網(wǎng)站建設(shè)、營銷型網(wǎng)站建設(shè)網(wǎng)站制作、靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司