通過前兩個系列我們把JVM相關(guān)的一些原理內(nèi)容并結(jié)合一些案例和GC工作原理給大家都介紹清楚了,那么本系列開始重點給大家?guī)鞪VM優(yōu)化相關(guān)內(nèi)容和實戰(zhàn)。
基于JVM系統(tǒng)運行的過程剖析
首先我們還是通過一步一圖的方式但校,將我們整個系統(tǒng)基于JVM跑起來后所涉及到的一些核心知識進行串聯(lián)以及總結(jié)回顧,這樣大家在頭腦里先有一個整體的思維過程啡氢,加深印象状囱,然后我們再結(jié)合一些案例來逐步講解如何針對一些系統(tǒng)進行優(yōu)化。
①對象進年輕代
系統(tǒng)啟動后空执,肯定會創(chuàng)建對象浪箭,對象肯定存在我們的JVM內(nèi)存,而且會分配到年輕代的Eden區(qū)中:
②年輕代裝滿觸發(fā)GC
當(dāng)系統(tǒng)不斷運行辨绊,對象越來越多奶栖,導(dǎo)致Eden無法裝下,那么就會觸發(fā)年輕代垃圾回收门坷,年輕代主要通過復(fù)制算法進行回收宣鄙。
那么如何進行復(fù)制呢?首先會通過可達性分析算法來標記哪些對象是GC Roots的默蚌,能被當(dāng)做GC ROOTS對象的一般兩種:
- 方法內(nèi)的局部變量
- 類的靜態(tài)變量
JVM會將存活的對象統(tǒng)一拷貝到幸存者區(qū)域也就是Survivor冻晤,占年輕代內(nèi)存的10%,如下圖:
注意:新生代一旦發(fā)生GC绸吸,那么必定會導(dǎo)致“Stop the World”鼻弧,以后簡稱 STW,讓系統(tǒng)停止運行锦茁。
③年輕代垃圾回收
此時垃圾回收線程開始工作攘轩,這里我們默認使用并發(fā)收集器 ParNew
④思考:年輕代GC回收對系統(tǒng)影響大不?
年輕代的GC回收基于復(fù)制算法码俩,一般這個過程都會非常的快度帮,比如幾十ms即可回收完畢,但是大家要注意的一點就是,年輕代的回收是會觸發(fā)STW笨篷,讓系統(tǒng)停止的瞳秽,不過一般我們實際線上部署,機器配置都是2核4G或4核8G率翅,只要給堆內(nèi)存分配的空間足夠练俐,一般來講不需要對年輕代的gc進行調(diào)優(yōu)。大多數(shù)情況下的系統(tǒng)冕臭,一般也就幾分鐘或者幾十分鐘才會進行一次新生代的GC痰洒,卡頓時間也較少,對于用戶而言幾乎沒有太大的感覺浴韭。
那么什么時候需要關(guān)注新生代的GC調(diào)優(yōu)呢丘喻?
當(dāng)我們系統(tǒng)部署在大內(nèi)存機器上的時候,比如32核64G的機器念颈,此時可能年輕代的內(nèi)存就會分配到40G泉粉!比如像Kafka、Elasticsearch之類的大數(shù)據(jù)系統(tǒng)榴芳,都是部署在這種大內(nèi)存的機器嗡靡,而且并發(fā)訪問量相當(dāng)高,那么有可能我們的Eden區(qū)1分鐘就會裝滿窟感,然后觸發(fā)一次MinorGC讨彼,而我們的年輕代基于復(fù)制算法,這個過程由于對象太龐大柿祈,標記復(fù)制也是需要耗費一定的時間哈误,像這樣大的內(nèi)存產(chǎn)生的對象可能回收就得要幾秒鐘,那么就會導(dǎo)致一個可怕的現(xiàn)象:系統(tǒng)每運行1分鐘就會導(dǎo)致卡死幾秒躏嚎,而我們的前端請求一般可能在2秒內(nèi)無響應(yīng)就直接反饋給用戶了蜜自,這種體驗肯定是相當(dāng)不好的。
如何解決大內(nèi)存機器的年輕代GC過慢問題卢佣?
答案就是:使用G1垃圾回收器
由于G1收集器可以設(shè)置一個停頓的時間重荠,比如默認200ms,那么基于G1收集器回收的特點虚茶,我們就可以讓系統(tǒng)每隔200ms就進行一次垃圾回收戈鲁,這樣可以讓我們的系統(tǒng)幾乎在不受影響的情況下,邊執(zhí)行邊回收垃圾嘹叫,同時也不會給用戶帶來卡頓的使用體驗婆殿。
因此小結(jié)下:對于年輕代的垃圾回收,一般不存在太大的問題待笑,對于大內(nèi)存機器我們使用G1回收期即可鸣皂,而我們更多需要關(guān)注和調(diào)優(yōu)的地方在于老年代抓谴!
⑤對象進老年代
首先 我們重新梳理下對象要進入老年代的幾個條件:
對象在新生代中熬過15次GC暮蹂,年齡達15歲進入老年代寞缝,這種對象一般較少一般都是系統(tǒng)中確實需要長期存在的核心組件。
-
動態(tài)年齡判斷規(guī)則仰泻,比如一次GC過后荆陆,Survivor區(qū)中的幾個年齡對象加起來超過Survivor區(qū)內(nèi)存的50%,比如年齡1 + 年齡2 + 年齡3的對象總和超過50%Survivor區(qū)集侯,此時就會把年齡3以上的對象放入老年代被啼。
這里補充說明下動態(tài)年齡規(guī)則判斷算法:Survivor區(qū)中的對象年齡從小到大進行累加,當(dāng)累加到X年齡時的總和大于50%(可以通過參數(shù) -XX:TargetSurvivorRatio=?來設(shè)置保留多少空間空間棠枉,默認值是50)浓体,那么比X大的都會進入老年代
大對象直接進老年代,可以通過參數(shù)-XX:PretenureSizeThreshold參數(shù) 設(shè)置
新生代垃圾回中存活對象太多無法放入Survivor中辈讶,通過空間擔(dān)保原則進入老年代
如果Survivor區(qū)空間太小命浴,就會導(dǎo)致我們以上的條件2和4頻繁發(fā)生,然后導(dǎo)致大量對象進入老年代贱除,從而頻繁觸發(fā)老年代的垃圾回收生闲。
當(dāng)然CMS的老年代回收,包括:初始標記月幌、并發(fā)標記碍讯、重新標記、并發(fā)清理扯躺、碎片整理等環(huán)節(jié)捉兴,如果有同學(xué)已經(jīng)忘記了請查看之前的CMS收集器內(nèi)容進行復(fù)習(xí)。
這里重點想說的是录语,一旦觸發(fā)老年代GC那么耗時至少比新生代GC慢10倍以上轴术,因此一旦JVM內(nèi)存分配不合理,導(dǎo)致頻繁觸發(fā)老年代CG對于系統(tǒng)用戶來說就是糟糕的體驗钦无。
因此我們常說的JVM優(yōu)化逗栽,到底在優(yōu)化什么?失暂?
你的代碼編寫是否有問題彼宠,內(nèi)存分配,參數(shù)設(shè)置是否合理弟塞,有沒有導(dǎo)致對象頻繁的進入老年代凭峡,然后頻繁觸發(fā)老年代GC,導(dǎo)致系統(tǒng)頻繁的每隔幾分鐘就要卡死幾秒决记!那么JVM性能優(yōu)化也就是優(yōu)化這些東西摧冀,盡量減少觸發(fā)老年代的GC這就是我們的目標。
⑥Yong GC/Minor GC 觸發(fā)時機
新生代的GC觸發(fā)相信大家應(yīng)該很熟悉了,一旦當(dāng)Eden區(qū)滿了后就會觸發(fā)索昂,并且采用復(fù)制算法來回收建车。
⑦OldGC和Full GC觸發(fā)時機
第一種情況:
老年代連續(xù)空間小于新生代歷次YongGC晉升到老年代的對象的平均值
之前針對老年代的空間擔(dān)保原則時我們畫過一張詳細的圖非常清晰,我們再次拿來回顧下:
注意這里為什么執(zhí)行的是Full GC呢而不是Old GC,理論上來說就是Old GC忱叭,只不過當(dāng)Old GC執(zhí)行完了后肯定還會執(zhí)行一次Yong GC隔崎,所以我們直接用了Full GC進行表示。
而且我們之前也明確說明了是在發(fā)生Minor GC之前對上述流程的一個判斷韵丑,因此其實不管最終走不走Old GC爵卒,最后都會執(zhí)行一次Minor GC,前提是沒有發(fā)生OOM異常撵彻。
第二種情況:
執(zhí)行YongGC之后钓株,有一批對象需要放入老年代,而此時老年代沒有足夠的內(nèi)存空間存放陌僵,此時必須觸發(fā)一次Old GC轴合。
就像上圖中所示一樣,假如JVM開啟冒險模式進行一次YongGC碗短,但是執(zhí)行完后了后發(fā)現(xiàn)老年代空間還是不足與存放受葛,那么此時必然觸發(fā)Old GC的執(zhí)行。
第三種情況:
當(dāng)老年代內(nèi)存使用超過了92%偎谁,也要直接觸發(fā)Old GC总滩,當(dāng)然這個比例是可以通過參數(shù)調(diào)整的。
總結(jié)一下就是:當(dāng)老年代空間不足巡雨,沒法放入更多的對象時闰渔,就會觸發(fā)Old GC進行垃圾回收。
其實大家也發(fā)現(xiàn)了铐望,我們上述在分析老年代垃圾回收的觸發(fā)時機時冈涧,也能知曉茂附,要么先進行新生代回收,再觸發(fā)老年代回收督弓,要么老年代回收后也會觸發(fā)一次MinorGC营曼。其實當(dāng)上述條件達到觸發(fā)的就是Full GC了,而不是單純的Old GC咽筋。在后續(xù)的實操案例中我們也可以通過日志觀察到,GC日志中顯示的就是Full GC徊件,而不是單純的Old GC奸攻,F(xiàn)ull GC的觸發(fā)是包含Yong GC,Old GC,永久代的GC。
一般來講永久代的空間都是足夠的虱痕,不會存在太多的垃圾睹耐,里面都是存放的一些常量池之類的數(shù)據(jù),并且StringTable從JDK7開始也移動到了堆內(nèi)存部翘,因此永久代一般來說可回收的垃圾很少硝训,如果真的出現(xiàn)了永久代滿了,那么系統(tǒng)也就直接OOM了新思。
如果大家能將以上內(nèi)容完全掌握理解窖梁,那么我相信大家的JVM理論核心知識點已經(jīng)掌握的非常不錯了!
后續(xù)我們結(jié)合一些工具和實操案例夹囚,或者結(jié)合各位自己當(dāng)下公司的系統(tǒng)來進行JVM調(diào)優(yōu)纵刘,分析,大家一定能徹底的將JVM給熟練于心荸哟,并且不會輕易忘記假哎,如果每次問到你JVM相關(guān)內(nèi)容,你還需要去看看筆記或者博客鞍历,說明當(dāng)時理解清楚了舵抹,并沒有實際運用到公司中。因此劣砍,后續(xù)的一些實際案例希望大家能結(jié)合自己的系統(tǒng)多去思考惧蛹,實操練習(xí)!