作者簡介:大家好,我是smart哥,前中興通訊、美團(tuán)架構(gòu)師,現(xiàn)某互聯(lián)網(wǎng)公司CTO
聯(lián)系qq:184480602,加我進(jìn)群,大家一起學(xué)習(xí),一起進(jìn)步,一起對抗互聯(lián)網(wǎng)寒冬
學(xué)習(xí)必須往深處挖,挖的越深,基礎(chǔ)越扎實!
階段1、深入多線程
階段2、深入多線程設(shè)計模式
階段3、深入juc源碼解析
階段4、深入jdk其余源碼解析
階段5、深入jvm源碼解析
一、簡介
本章和下一章,我們將通過一個實際案例講解如何進(jìn)行JVM參數(shù)調(diào)優(yōu):合理優(yōu)化新生代、老年代、Eden和Survivor各個區(qū)域的內(nèi)存大小,接著再盡量優(yōu)化參數(shù)避免新生代的對象進(jìn)入老年代,盡量讓對象留在新生代里被回收掉。
本章先針對新生代調(diào)優(yōu)進(jìn)行講解,后續(xù)章節(jié),我們會針對老年代調(diào)優(yōu)再專門講解,新生代調(diào)優(yōu)的整個過程都圍繞以下幾點(diǎn)來思考:
- 每秒占用多少內(nèi)存?
- 多長時間觸發(fā)一次Minor GC?
- 每次Minor GC后,存活對象的平均大小是多少?
- Survivor能否容納存活對象?
- 會不會因為Survivor無法容納頻繁進(jìn)入老年代?
- 會不會因為動態(tài)年齡判斷規(guī)則進(jìn)入老年代?
我們先來看下案例的背景。
1.1 案例背景
假設(shè)生產(chǎn)環(huán)境有一個每日上億訪問量的電商系統(tǒng),日平均訂單量50萬。在雙11等大促場景下,峰值為每秒1000筆訂單?,F(xiàn)在部署3臺4核8G的機(jī)器,每臺每秒抗300筆訂單請求。
我們的目標(biāo)就是對JVM有限的內(nèi)存資源進(jìn)行合理的分配和優(yōu)化,讓JVM的Minor GC次數(shù)盡量少,同時盡量避免Full GC。
1.2 內(nèi)存使用模型估算
交代完了背景,我們再來估算下高峰時期的JVM內(nèi)存使用模型:
一筆訂單一般20個左右的字段,按1kb算,那1秒鐘300筆訂單就是300kb,然后還需要算上其它業(yè)務(wù)對象(比如庫存、積分、商戶等等),所以放大20倍。此外,訂單系統(tǒng)還有其它操作,特別是查詢操作,同樣耗費(fèi)內(nèi)存,所以再放大10倍,那么每秒鐘的內(nèi)存總開銷就是:
$$
300kb2010=60MB
$$
我們假設(shè)1s過后,這60MB的對象都會失去引用變成垃圾。
二、JVM內(nèi)存分配
2.1 初始情況
4核8G的機(jī)器,一般分配4G給JVM。假設(shè)最初狀態(tài)下,Java堆內(nèi)存分配3G(新生代1.5G,老年代1.5G),每個線程的??臻g1MB,一個JVM大概幾百個線程,所以總共給虛擬機(jī)棧幾百兆,然后再給永久代256MB:
-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8
參數(shù)-XX:SurvivorRatio
采用默認(rèn)值8,即Eden和Survivor按照默認(rèn)的8:1:1分配內(nèi)存。
空間擔(dān)保機(jī)制的JVM參數(shù)
-XX:HandlePromotionFailure
在JDK1.6以后就被廢棄了,JDK1.6以后只需要判斷“老年代可用空間 > 新生代對象總和”或“老年代可用空間 > 歷次Minor GC晉升到老年代的對象平均大小”,這兩個條件滿足任意一個,就可以直接進(jìn)行Minor GC,不會提前觸發(fā)Full GC。
2.2 優(yōu)化Survivor空間
系統(tǒng)運(yùn)行期間,每秒都會在新生代產(chǎn)生60MB對象,然后1秒后就失去引用,大約25左右,新生代中Eden區(qū)的1.2G空間就會被占滿:
此時,就會觸發(fā)是否進(jìn)行Minor GC的判斷,由于老年代可用空間(1.5G) > 歷次Minor GC晉升到老年代的對象平均大小(0G),所以Minor GC直接運(yùn)行,一下子回收99%的新生代中的垃圾,除了最近1秒的還在處理,剩下的都處理完了,總共余留大約100MB存活對象,存放對象放入S1區(qū),然后清空Eden:
然后,再運(yùn)行20秒后,Eden區(qū)再次被占滿,再次觸發(fā)并執(zhí)行Minor GC,存放對象放入S2,同時清空Eden和S1:
按照上面的測算,每次存活對象的平均大小為100MB,Survivor區(qū)的大小為150MB,非常接近,很可能出現(xiàn)某次Minor GC后存活對象超過了150MB,導(dǎo)致Survivor區(qū)無法容納,使得存活對象晉升到老年代。
此外,每次存活對象的年齡是相同的,根據(jù)動態(tài)年齡規(guī)則,100MB超過了Survivor的50%,所以很可能這100MB存放對象會直接晉升到老年代。
所以,Survivor區(qū)只分配150MB空間是不夠的。所以可以考慮?把新生代調(diào)整為2G,老年代調(diào)整為1G,這樣每個Survivor就有200MB空間?:
此時,JVM參數(shù)如下:
-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8
對于任何系統(tǒng),應(yīng)盡量讓每次Minor GC后的對象都留在Survivor中,不要進(jìn)入老年代,所以Survivor區(qū)是首要優(yōu)化點(diǎn)。
2.3 是否優(yōu)化晉升年齡
默認(rèn)情況,新生代存活對象的年齡到達(dá)15后,就會進(jìn)入老年代,那么我們是否要通過參數(shù)-XX:MaxTenuringThreshold
來調(diào)整年齡的上限值,以避免對象進(jìn)行老年代呢?
答案是否定的,一般來說,經(jīng)歷了15次Minor GC還存在于新生代的,都是那些需要長期存活的核心業(yè)務(wù)對象,比如@Service、@Controller等注解的對象。對于這些對象,它們就應(yīng)該進(jìn)入老年代,況且這類對象的數(shù)量不會很多,一般一個系統(tǒng)也就是幾十MB而已。
所以,反而可以降級晉升年齡,我們這里設(shè)置成5,即-XX:MaxTenuringThreshold=5。
2.4 是否優(yōu)化大對象
大對象是可以直接進(jìn)入老年代的,一般來說-XX:PretenureSizeThreshold
這個閾值設(shè)置成1MB足以,因為很少有超過1MB的大對象。如果有,可能是你提前分配了一個大數(shù)組或集合之類的對象,用以存放緩存。
2.5 指定垃圾回收器
最后,還要指定垃圾回收器,新生代用ParNew,老年代用CMS,最終,JVM的參數(shù)設(shè)置如下:
-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
ParNew垃圾收集器的核心參數(shù),其實就是配套的新生代內(nèi)存大小,Eden和Survivor的比例,只要你設(shè)置合理,避免存活對象放不下Survivor而進(jìn)入老年代,或者是動態(tài)年齡判斷后進(jìn)入老年代,那么Minor GC一般不會有什么問題。
三、總結(jié)
本章通過一個電商示例,分析了JVM新生代的參數(shù)優(yōu)化,核心優(yōu)化目的就是盡量讓每次存活的對象都進(jìn)入Survivor,避免進(jìn)入老年代。文章來源:http://www.zghlxwxcb.cn/news/detail-794881.html
本章主要針對新生代進(jìn)行優(yōu)化,下一章我們將結(jié)合案例分析老年代的垃圾回收和JVM參數(shù)優(yōu)化。文章來源地址http://www.zghlxwxcb.cn/news/detail-794881.html
到了這里,關(guān)于JVM基礎(chǔ)(9)——新生代調(diào)優(yōu)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!