JVM調優(一)預估調優

1、引子

魏文王問扁鵲:你們三兄弟都精通醫術,誰是醫術最好的呢?扁鵲回答:大哥最好,二哥次之,我最差。魏文王不解的問:爲什麼這樣說呢?扁鵲答:大哥治病是在病人發作之前,那時候病人自己不覺得有病,但大哥就下藥剷除了病根,使他的醫術難以被人認可,所以沒有名氣;二哥治病是在病起之初,症狀尚不十分明顯,病人也沒有覺得痛苦,二哥就能藥到病除,所以大家的印象就是小病找二哥;我治病是在病人危急時刻,病人痛苦萬分,家人心急如焚,他們看到我治病時在經脈上扎針穿刺,或以毒試毒,或動大手續使病人減輕痛苦直至痊癒,所以我聞名天下。魏文王大悟。

2、正題

爲什麼要講這個故事呢?
因爲JVM調優跟這個故事很像。JVM調優也有這三個階段:

1、在項目部署到線上之前,基於可能的併發量進行預估調優

2、在項目運行過程中,部署監控收集性能數據,平時分析日誌進行調優

3、線上出現OOM,進行問題排查與調優
這裏每一點展開來講涉及到的知識點都比較多,打算用三篇文章講清楚每一階段的調優,本篇文章講第一階段,主要討論堆區調優。

3、JVM調優開始

3.1、爲什麼要做JVM調優?
1、防止出現OOM
即在系統部署之前,根據一些關鍵數據進行預估不同內存區域需要給多少內存合適
2、解決OOM
即線上出現了OOM,應該如何調優以保證程序能正常運行
3、減少full gc出現的頻率
這個主要是堆區,如果設置的不合理就會頻繁full gc,導致系統運行一陣暫停一陣,導致體驗下降

4、場景

這裏以億級流量秒殺電商系統爲例:

如果每個用戶平均訪問20個商品詳情頁,那訪客數約等於500w(一億 / 20)

如果按轉化率10%來算,那日均訂單約等於50w(500w * 10%)

如果40%的訂單是在秒殺前兩分鐘完成的,那麼每秒產生1200筆訂單(50w * 30% / 120s)

訂單支付又涉及到發起支付流程、物流、優惠券、推薦、積分等環節,導致產生大量對象,這裏我們假設整個支付流程生成的對象約等於20K,那每秒在Eden區生成的對象約等於20M(1200筆 * 20K)

在生產環境中,訂單模塊還涉及到百萬商家查詢訂單、改價、包郵、發貨等其他操作,又會產生大量對象,我們放大10倍,即每秒在Eden區生成的對象約等於200M(其實這裏就是在大併發場景下可以考慮服務降級的地方,架構其實就是取捨)

這裏的假設數據都是大部分電商系統的通用概率,是有一定代表性的。
如果你作爲這個系統的架構師,面對這樣的場景,你會如何做JVM調優呢?即將運行該系統的JVM堆區設置成多大呢?

5、前置知識

1、瞭解Java中基本數據類型的字節長度

public class TestLength {
    public static void main(String[] args) {
        System.out.println("Byte.SIZE=" + Byte.SIZE / 8);
        System.out.println("Character.SIZE=" + Character.SIZE / 8);
        System.out.println("Short.SIZE=" + Short.SIZE / 8);
        System.out.println("Integer.SIZE=" + Integer.SIZE / 8);
        System.out.println("Long.SIZE=" + Long.SIZE / 8);
        System.out.println("Float.SIZE=" + Float.SIZE / 8);
        System.out.println("Double.SIZE=" + Double.SIZE / 8);
    }
}

2、深刻理解如何計算對象大小及指針壓縮
3、深刻理解JVM內存模型
計算對象大小以及指針壓縮傳送門
4、動態對象年齡判斷

JVM並不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
垃圾判斷算法
gc過程中需要知道哪些對象能回收,哪些對象不能回收,那如何判斷呢?就是靠這個算法。這裏我就不展開講了,因爲學JVM實戰的同學我相信對這兩種算法都很瞭解了。如果不瞭解的同學可以百度搜點文章看看,這塊的文章搜出來的基本還算靠譜。

1、引用計數法

2、可達性分析算法(GC Root)
如何調優

這裏我們以內存32G服務器來計算。JVM的堆區最大值默認是物理內存的四分之一,即8G。新生代、老年代的比例默認是1:2,即新生代約等於2.7G,老年代約等於5.4G。新生代中Eden區、From區、To區的比例是8:1:1,即Eden區約等於2.2G,From區、To區各佔約270M。

前面我們計算了億級流量併發系統在高峯期時Eden區每秒產生大約200M的對象,如果在部署系統時未對JVM做任何調優,那麼系統運行11s左右(2200M),Eden區就會被充滿,就會產生young gc。一般來說整個付款環節3s完成算快的,我們這裏就按3s來計算,那麼在產生young gc時,Eden區有約600M的對象無法回收,因爲600M已超過To區的大小,會觸發空間擔保機制,這600M的對象會直接被移入老年代。按照這個節奏,程序運行一分半種多點就會產生full gc,引起STW,這在付款環節是不可接受的,所以我們需要做調優。

6、如何做調優

我們需要反向來推理:爲了保證不觸發空間擔保機制,From區、To區都設置成600M,那個新生代就是6G、老年代就是12G。這樣一個JVM就喫掉了18G的內存,所以還需要做其他事情:將這個服務器上其他比較喫內存的服務移走,以保證其他服務的正常運行。這裏你爲了保險起見,你可以設置得更大,具體調優的數值各位童鞋視實際情況而定吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章