JVM GC原理及調(diào)優(yōu)的基本思路

和Web應(yīng)用程序一樣,Tomcat作為一個(gè)Java程序也跑在JVM中淮悼,因此如果要對(duì)Tomcat進(jìn)行調(diào)優(yōu)煞额,需要先了解JVM調(diào)優(yōu)的原理思恐。而對(duì)于JVM調(diào)優(yōu)來說,主要是JVM垃圾收集的優(yōu)化膊毁,一般來說是因?yàn)橛袉栴}才需要優(yōu)化胀莹,所以對(duì)于JVM GC來說,如果觀察到Tomcat進(jìn)程的CPU使用率比較高婚温,并且在GC日志中發(fā)現(xiàn)GC次數(shù)比較頻繁描焰、GC停頓時(shí)間長,這表明需要對(duì)GC進(jìn)行優(yōu)化了栅螟。

在對(duì)GC調(diào)優(yōu)的過程中荆秦,不僅需要知道GC的原理,更重要的是要熟練使用各種監(jiān)控和分析工具力图,具備GC調(diào)優(yōu)的實(shí)戰(zhàn)能力步绸。CMS和G1是時(shí)下使用率比較高的兩款垃圾收集器,從Java 9開始吃媒,采用G1作為默認(rèn)垃圾收集器瓤介,而G1的目標(biāo)也是逐步取代CMS。

1.CMS vs G1

CMS收集器將Java堆分為年輕代(Young)或年老代(Old)赘那。這主要是因?yàn)橛醒芯勘砻餍躺#^90%的對(duì)象在第一次GC時(shí)就被回收掉,但是少數(shù)對(duì)象往往會(huì)存活較長的時(shí)間募舟。

CMS還將年輕代內(nèi)存空間分為幸存者空間(Survivor)和伊甸園空間(Eden)祠斧。新的對(duì)象始終在Eden空間上創(chuàng)建。一旦一個(gè)對(duì)象在一次垃圾收集后還幸存拱礁,就會(huì)被移動(dòng)到幸存者空間琢锋。當(dāng)一個(gè)對(duì)象在多次垃圾收集之后還存活時(shí)辕漂,它會(huì)移動(dòng)到年老代。這樣做的目的是在年輕代和年老代采用不同的收集算法吴超,以達(dá)到較高的收集效率钮热,比如在年輕代采用復(fù)制-整理算法,在年老代采用標(biāo)記-清理算法烛芬。因此CMS將Java堆分成如下區(qū)域:

與CMS相比隧期,G1收集器有兩大特點(diǎn):

  • G1可以并發(fā)完成大部分GC的工作,這期間不會(huì)“Stop-The-World”赘娄。
  • G1使用非連續(xù)空間仆潮,這使G1能夠有效地處理非常大的堆。此外遣臼,G1可以同時(shí)收集年輕代和年老代性置。G1并沒有將Java堆分成三個(gè)空間(Eden、Survivor和Old)揍堰,而是將堆分成許多(通常是幾百個(gè))非常小的區(qū)域鹏浅。這些區(qū)域是固定大小的(默認(rèn)情況下大約為2MB)。每個(gè)區(qū)域都分配給一個(gè)空間屏歹。 G1收集器的Java堆如下圖所示:

圖上的U表示“未分配”區(qū)域隐砸。G1將堆拆分成小的區(qū)域,一個(gè)最大的好處是可以做局部區(qū)域的垃圾回收蝙眶,而不需要每次都回收整個(gè)區(qū)域比如年輕代和年老代季希,這樣回收的停頓時(shí)間會(huì)比較短。具體的收集過程是:

  • 將所有存活的對(duì)象將從收集的區(qū)域復(fù)制到未分配的區(qū)域幽纷,比如收集的區(qū)域是Eden空間式塌,把Eden中的存活對(duì)象復(fù)制到未分配區(qū)域,這個(gè)未分配區(qū)域就成了Survivor空間友浸。理想情況下峰尝,如果一個(gè)區(qū)域全是垃圾(意味著一個(gè)存活的對(duì)象都沒有),則可以直接將該區(qū)域聲明為“未分配”收恢。
  • 為了優(yōu)化收集時(shí)間武学,G1總是優(yōu)先選擇垃圾最多的區(qū)域,從而最大限度地減少后續(xù)分配和釋放堆空間所需的工作量派诬。這也是G1收集器名字的由來——Garbage-First劳淆。

2.GC調(diào)優(yōu)原則

GC是有代價(jià)的链沼,因此調(diào)優(yōu)的根本原則是每一次GC都回收盡可能多的對(duì)象默赂,也就是減少無用功。因此在做具體調(diào)優(yōu)的時(shí)候括勺,針對(duì)CMS和G1兩種垃圾收集器缆八,分別有一些相應(yīng)的策略曲掰。

CMS收集器

對(duì)于CMS收集器來說,最重要的是合理地設(shè)置年輕代和年老代的大小奈辰。年輕代太小的話栏妖,會(huì)導(dǎo)致頻繁的Minor GC,并且很有可能存活期短的對(duì)象也不能被回收奖恰,GC的效率就不高吊趾。而年老代太小的話,容納不下從年輕代過來的新對(duì)象瑟啃,會(huì)頻繁觸發(fā)單線程Full GC论泛,導(dǎo)致較長時(shí)間的GC暫停,影響Web應(yīng)用的響應(yīng)時(shí)間蛹屿。

G1收集器

對(duì)于G1收集器來說屁奏,不推薦直接設(shè)置年輕代的大小,這一點(diǎn)跟CMS收集器不一樣错负,這是因?yàn)镚1收集器會(huì)根據(jù)算法動(dòng)態(tài)決定年輕代和年老代的大小坟瓢。因此對(duì)于G1收集器,需要關(guān)心的是Java堆的總大杏倘觥(-Xmx)折联。

此外G1還有一個(gè)較關(guān)鍵的參數(shù)是-XX:MaxGCPauseMillis = n,這個(gè)參數(shù)是用來限制最大的GC暫停時(shí)間识颊,目的是盡量不影響請(qǐng)求處理的響應(yīng)時(shí)間崭庸。G1將根據(jù)先前收集的信息以及檢測到的垃圾量,估計(jì)它可以立即收集的最大區(qū)域數(shù)量谊囚,從而盡量保證GC時(shí)間不會(huì)超出這個(gè)限制怕享。因此G1相對(duì)來說更加“智能”,使用起來更加簡單镰踏。

3.內(nèi)存調(diào)優(yōu)實(shí)戰(zhàn)

下面通過一個(gè)例子實(shí)戰(zhàn)一下Java堆設(shè)置得過小函筋,導(dǎo)致頻繁的GC,將通過GC日志分析工具來觀察GC活動(dòng)并定位問題奠伪。

1.首先我們建立一個(gè)Spring Boot程序跌帐,作為調(diào)優(yōu)對(duì)象,代碼如下:

@RestController
public class GcTestController {

    private Queue<Greeting> objCache =  new ConcurrentLinkedDeque<>();

    @RequestMapping("/greeting")
    public Greeting greeting() {
        Greeting greeting = new Greeting("Hello World!");

        if (objCache.size() >= 200000) {
            objCache.clear();
        } else {
            objCache.add(greeting);
        }
        return greeting;
    }
}

@Data
@AllArgsConstructor
class Greeting {
   private String message;
}

上面的代碼就是創(chuàng)建了一個(gè)對(duì)象池绊率,當(dāng)對(duì)象池中的對(duì)象數(shù)到達(dá)200000時(shí)才清空一次谨敛,用來模擬年老代對(duì)象。

2.用下面的命令啟動(dòng)測試程序:

java -Xmx32m -Xss256k -verbosegc -Xlog:gc*,gc+ref=debug,gc+heap=debug,gc+age=trace:file=gc-%p-%t.log:tags,uptime,time,level:filecount=2,filesize=100m -jar target/demo-0.0.1-SNAPSHOT.jar

給程序設(shè)置的堆的大小為32MB滤否,目的是能看到Full GC脸狸。除此之外,還打開了verbosegc日志,請(qǐng)注意這里使用的版本是Java 12炊甲,默認(rèn)的垃圾收集器是G1泥彤。

3.使用JMeter壓測工具向程序發(fā)送測試請(qǐng)求,訪問的路徑是/greeting卿啡。

4.使用GCViewer工具打開GC日志吟吝,可以看到這樣的圖:

解釋一下這張圖:

  • 圖中上部的藍(lán)線表示已使用堆的大小,看到它周期的上下震蕩颈娜,這是因?yàn)閷?duì)象池要擴(kuò)展到200000才會(huì)清空剑逃。
  • 圖底部的綠線表示年輕代GC活動(dòng),從圖上看到當(dāng)堆的使用率上去了官辽,會(huì)觸發(fā)頻繁的GC活動(dòng)炕贵。
  • 圖中的豎線表示Full GC,從圖上看到野崇,伴隨著Full GC称开,藍(lán)線會(huì)下降,這說明Full GC收集了年老代中的對(duì)象乓梨。

基于上面的分析鳖轰,可以得出一個(gè)結(jié)論,那就是Java堆的大小不夠扶镀。解釋一下為什么得出這個(gè)結(jié)論:

  • GC活動(dòng)頻繁:年輕代GC(綠色線)和年老代GC(黑色線)都比較密集蕴侣。這說明內(nèi)存空間不夠,也就是Java堆的大小不夠臭觉。
  • Java的堆中對(duì)象在GC之后能夠被回收昆雀,說明不是內(nèi)存泄漏。

通過GCViewer還發(fā)現(xiàn)累計(jì)GC暫停時(shí)間有55.57秒蝠筑,如下圖所示:

因此解決方案是調(diào)大Java堆的大小狞膘,像下面這樣:

java -Xmx2048m -Xss256k -verbosegc -Xlog:gc*,gc+ref=debug,gc+heap=debug,gc+age=trace:file=gc-%p-%t.log:tags,uptime,time,level:filecount=2,filesize=100m -jar target/demo-0.0.1-SNAPSHOT.jar

生成的新的GC log分析圖如下:

可以看到,沒有發(fā)生Full GC什乙,并且年輕代GC也沒有那么頻繁了挽封,并且累計(jì)GC暫停時(shí)間只有3.05秒。

總結(jié)

首先回顧了CMS和G1兩種垃圾收集器背后的設(shè)計(jì)思路以及它們的區(qū)別臣镣,接著分析了GC調(diào)優(yōu)的總體原則辅愿。

對(duì)于CMS來說,要合理設(shè)置年輕代和年老代的大小忆某。該如何確定它們的大小呢点待?這是一個(gè)迭代的過程,可以先采用JVM的默認(rèn)值弃舒,然后通過壓測分析GC日志癞埠。

如果看年輕代的內(nèi)存使用率處在高位,導(dǎo)致頻繁的Minor GC,而頻繁GC的效率又不高燕差,說明對(duì)象沒那么快能被回收,這時(shí)年輕代可以適當(dāng)調(diào)大一點(diǎn)坝冕。

如果看年老代的內(nèi)存使用率處在高位徒探,導(dǎo)致頻繁的Full GC,這樣分兩種情況:如果每次Full GC后年老代的內(nèi)存占用率沒有下來喂窟,可以懷疑是內(nèi)存泄漏测暗;如果Full GC后年老代的內(nèi)存占用率下來了,說明不是內(nèi)存泄漏磨澡,要考慮調(diào)大年老代碗啄。

對(duì)于G1收集器來說,可以適當(dāng)調(diào)大Java堆稳摄,因?yàn)镚1收集器采用了局部區(qū)域收集策略稚字,單次垃圾收集的時(shí)間可控,可以管理較大的Java堆厦酬。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胆描,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子仗阅,更是在濱河造成了極大的恐慌昌讲,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件减噪,死亡現(xiàn)場離奇詭異短绸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)筹裕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門醋闭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朝卒,你說我怎么就攤上這事目尖。” “怎么了扎运?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瑟曲,是天一觀的道長。 經(jīng)常有香客問我豪治,道長洞拨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任负拟,我火速辦了婚禮烦衣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己花吟,他們只是感情好秸歧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衅澈,像睡著了一般键菱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上今布,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天经备,我揣著相機(jī)與錄音,去河邊找鬼部默。 笑死侵蒙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的傅蹂。 我是一名探鬼主播纷闺,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼份蝴!你這毒婦竟也來了急但?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤搞乏,失蹤者是張志新(化名)和其女友劉穎波桩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體请敦,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡镐躲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侍筛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萤皂。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匣椰,靈堂內(nèi)的尸體忽然破棺而出裆熙,到底是詐尸還是另有隱情,我是刑警寧澤禽笑,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布入录,位于F島的核電站,受9級(jí)特大地震影響佳镜,放射性物質(zhì)發(fā)生泄漏僚稿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一蟀伸、第九天 我趴在偏房一處隱蔽的房頂上張望蚀同。 院中可真熱鬧缅刽,春花似錦、人聲如沸蠢络。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刹孔。三九已至啡省,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芦疏,已是汗流浹背冕杠。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工微姊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酸茴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓兢交,卻偏偏與公主長得像薪捍,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子配喳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理酪穿,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 15,595評(píng)論 3 83
  • 轉(zhuǎn)載blog.csdn.net/ning109314/article/details/10411495/ JVM工...
    forever_smile閱讀 5,366評(píng)論 1 56
  • 作者:一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-12】 更新日志 日期更新內(nèi)容備注 2017-11-12新建文章初版 ...
    beneke閱讀 2,202評(píng)論 0 7
  • Java 虛擬機(jī)有自己完善的硬件架構(gòu), 如處理器晴裹、堆棧被济、寄存器等,還具有相應(yīng)的指令系統(tǒng)涧团。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,687評(píng)論 0 10
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,423評(píng)論 1 0