面試官:你這JVM調(diào)優(yōu),回答的很有問題呀!!
點(diǎn)擊上方“Java技術(shù)江湖”,選擇“設(shè)為星標(biāo)”
回復(fù)”666“獲取全網(wǎng)最熱的Java核心知識(shí)點(diǎn)整理
來源:https://juejin.cn/post/7128377003224334373
1.寫在前面
前段時(shí)間一位讀者面了阿里,在二面中被問到 GC 日志分析,感覺回答的不是很好,過來找我復(fù)盤,大致聽了他的回答,雖然回答出了部分,但是沒抓到重點(diǎn)。
GC 日志分析算是 JVM 調(diào)優(yōu)中比較難的部分,今天這篇文章就來聊聊如何利用 JDK 現(xiàn)有的命令并且借助可視化工具如何去分析 GC 日志。
2.JVM 調(diào)優(yōu)實(shí)踐
2.1 JVM 實(shí)踐調(diào)優(yōu)主要步驟
默認(rèn)的策略是最普用,但不是最佳的。
第一步:監(jiān)控分析 GC 日志 第二步:判斷 JVM 問題 : 如果各項(xiàng)參數(shù)設(shè)置合理,系統(tǒng)沒有超時(shí)日志出現(xiàn),GC 頻率不高,GC 耗時(shí)不高,那么沒有必要進(jìn)行 GC 優(yōu)化 如果 GC 時(shí)間超過 1-3 秒,或者頻繁 GC,則必須優(yōu)化。 第三步:確定調(diào)優(yōu)目標(biāo) 第四步:調(diào)整參數(shù) 調(diào)優(yōu)一般是從滿足程序的內(nèi)存使用需求開始,之后是時(shí)間延遲要求,最后才是吞吐量要求,要基于這個(gè)步驟來不斷優(yōu)化,每一個(gè)步驟都是進(jìn)行下一步的基礎(chǔ),不可逆行之。 第五步:對比調(diào)優(yōu)前后差距 第六步:重復(fù):1 、 2 、 3 、 4 、 5 步驟 找到最佳 JVM 參數(shù)設(shè)置 第七步:應(yīng)用 JVM 到應(yīng)用服務(wù)器 : 找到最合適的參數(shù),將這些參數(shù)應(yīng)用到所有服務(wù)器,并進(jìn)行后續(xù)跟蹤。
以上,就是我們進(jìn)行 jvm 調(diào)優(yōu)得一些步驟了。
那我們就從第一步開始嘍!!!^_^
2.2 分析 GC 日志
2.2.1 初始參數(shù)設(shè)置
機(jī)器環(huán)境:
| 指標(biāo) | 參數(shù) |
|---|---|
| 機(jī)器 | CPU 12 核,內(nèi)存 16GB |
| 集群規(guī)模 | 單機(jī) |
| seqb_web 版本 | 1.0 |
| 數(shù)據(jù)庫 | 4 核 16G |
Jvm 調(diào)優(yōu)典型參數(shù)設(shè)置;
-Xms 堆內(nèi)存的最小值 :默認(rèn)情況下,當(dāng)堆中可用內(nèi)存小于 40%時(shí),堆內(nèi)存會(huì)開始增加,一直增加到-Xmx 的大小。 -Xmx 堆內(nèi)存的最大值:默認(rèn)值是總內(nèi)存/64(且小于 1G),默認(rèn)情況下,當(dāng)堆中可用內(nèi)存大于 70%時(shí),堆內(nèi)存會(huì)開始減少,一直減小到-Xms 的大小; -Xmn 新生代內(nèi)存的最大值:包括 Eden 區(qū)和兩個(gè) Survivor 區(qū)的總和,配置寫法如:-Xmn1024k,-Xmn1024m,-Xmn1g -Xss 每個(gè)線程的棧內(nèi)存:默認(rèn) 1M,一般來說是不需要改。線程棧越小意味著可以創(chuàng)建的線程數(shù)越多
整個(gè)堆的大小 = 年輕代大小 + 年老代大小,堆的大小不包含元空間大小,如果增大了年輕代,年老代相應(yīng)就會(huì)減小,官方默認(rèn)的配置為年老代大小/年輕代大小=2/1 左右;
建議在開發(fā)測試環(huán)境可以用 Xms 和 Xmx 分別設(shè)置最小值最大值,但是在線上生產(chǎn)環(huán)境,Xms 和 Xmx 設(shè)置的值必須一樣,防止抖動(dòng);
這里比較重要喔,一般我們都是將 Xms 和 Xmx 的值設(shè)置為一樣的!!!

JVM 調(diào)優(yōu)設(shè)置合大小堆內(nèi)存空間,既不能太大,也不能太小。那么應(yīng)該設(shè)置為多少呢?
默認(rèn)的配置是否存在性能瓶頸。如果想要確定 JVM 性能問題瓶頸,需要進(jìn)一步分析GC 日志
-XX:+PrintGCDetails 開啟 GC 日志創(chuàng)建更詳細(xì)的 GC 日志 ,默認(rèn)情況下,GC 日志是關(guān)閉的 -XX:+PrintGCTimeStamps,-XX:+PrintGCDateStamps :開啟 GC 時(shí)間提示 開啟時(shí)間便于我們更精確地判斷幾次 GC 操作之間的時(shí)兩個(gè)參數(shù)的區(qū)別 時(shí)間戳是相對于 0 (依據(jù) JVM 啟動(dòng)的時(shí)間)的值,而日期戳(date stamp)是實(shí)際的日期字符串 由于日期戳需要進(jìn)行格式化,所以它的效率可能會(huì)受輕微的影響,不過這種操作并不頻繁,它造成的影響也很難被我們感知。 -XX:+PrintHeapAtGC 打印堆的 GC 日志 -Xloggc:./logs/gc.log 指定 GC 日志路徑
這里,我們是在 windows 下面進(jìn)行測試,idea 配置如下:

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log
這樣就會(huì)在 e 盤下 logs 文件夾下面,生成 gc-default.log 日志

2.2.2 GC 日志解讀
Young GC 日志含義
2022-08-05T13:45:23.336+0800: 4.866: [GC (Metadata GC Threshold) [PSYoungGen: 136353K->20975K(405504K)] 160049K->48437K(720384K), 0.0092260 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
這里的內(nèi)容,我們一個(gè)一個(gè)解析:
2022-08-05T13:45:23.336+0800: 本次GC發(fā)生時(shí)間
4.866: 舉例啟動(dòng)應(yīng)用的時(shí)間
[GC【表示GC的類型,youngGC】 (Metadata GC Threshold) 元空間超閾值
[PSYoungGen: 136353K->20975K(405504K年輕代總空間)] 160049K->48437K(720384K)整堆), 0.0092260 secs本次垃圾回收耗時(shí)]
[Times: user=0.00本次GC消耗CPU的時(shí)間 sys=0.02系統(tǒng)暫停時(shí)間, real=0.02 secs實(shí)際應(yīng)用暫停時(shí)間]
這里的解析,應(yīng)該很詳細(xì)了吧,還有誰看不懂的呢?

FullGC 日志含義
2022-08-05T20:24:47.815+0800: 6.955: [Full GC (Metadata GC Threshold) [PSYoungGen: 701K->0K(72704K)] [ParOldGen: 38678K->35960K(175104K)] 39380K->35960K(247808K), [Metaspace: 56706K->56706K(1099776K)], 0.1921975 secs] [Times: user=1.03 sys=0.00, real=0.19 secs]
這里的內(nèi)容,我們也是一個(gè)一個(gè)解析:
2022-08-05T20:24:47.815+0800:
6.955: 剛啟動(dòng)服務(wù)就Full GC【整堆回收!!】
[Full GC (Metadata GC Threshold) Metaspace空間超限!
[PSYoungGen: 701K->0K(72704K)] 年輕代沒有回收空間
[ParOldGen: 38678K->35960K(175104K)] 39380K->35960K(247808K), 老年代也沒有到閾值,整堆更沒有到閾值
[Metaspace: 56706K->56706K(1099776K)], 0.1921975 secs]
[Times: user=1.03本次GC消耗CPU的時(shí)間 sys=0.00系統(tǒng)暫停時(shí)間, real=0.19 secs實(shí)際應(yīng)用暫停時(shí)間]
看到這里,有些哥們就會(huì)說,這么看,也太惡心了吧,密密麻麻的日志,看著頭疼!!!

那么接下來我們來學(xué)一個(gè) GC 日志可視化工具

2.2.3 GC 日志可視化分析
分析 GC 日志,就必須讓 GC 日志輸出到一個(gè)文件中,然后使用 GC 日志分析工具(gceasy.io/) 進(jìn)行分析

這里分析完之后,可以下載分析報(bào)告

1) JVM 內(nèi)存占用情況:

| Generation【區(qū)域】 | Allocated【最大值】 | Peak【占用峰值】 |
|---|---|---|
| Young Generation【年輕代】 | 74.5 mb | 74.47 mb |
| Old Generation【老年輕代】 | 171 mb | 95.62 mb |
| Meta Space【元空間】 | 1.05 gb | 55.38 mb |
| Young + Old + Meta space【整體】 | 1.3 gb | 212.64 mb |
2) 關(guān)鍵性能指標(biāo):

1 、吞吐量:百分比越高表明 GC 開銷越低。這個(gè)指標(biāo)反映了 JVM 的吞吐量。
Throughput:97.043%
2 、GC 延遲:Latency
Avg Pause GC Time:7.80 ms 平均 GC 暫停時(shí)間 Max Pause GC Time:190 ms 最大 GC 暫停時(shí)間
3) GC 可視化交互聚合結(jié)果

由上圖可以看到,發(fā)生了 3 次 full gc
存在問題:一開始就發(fā)生了 3 次 full gc , 很明顯不太正常;
4) GC 統(tǒng)計(jì)

GC Statistics:GC 統(tǒng)計(jì)
由上圖可以得到,發(fā)生 gc 的總次數(shù),young gc,full gc 的統(tǒng)計(jì),gc 暫停時(shí)間統(tǒng)計(jì)。
5) GC 原因:

| 原因 | 次數(shù) | 平均時(shí)間 | 最大時(shí)間 | 總耗時(shí) |
|---|---|---|---|---|
| Metadata GC Threshold | 6 | 43.3 ms | 190 ms | 260 ms |
| Allocation Failure | 53 | 3.77 ms | 10.0 ms | 200 ms |
這里對這些原因解析一下:
Metadata GC Threshold:元空間超閾值 Allocation Failure :年輕代空間不足
這里補(bǔ)充一個(gè)原因,本案例還沒出現(xiàn)的:
Ergonomics:譯文是“人體工程學(xué)”,GC 中的 Ergonomics 含義是負(fù)責(zé)自動(dòng)調(diào)解 gc 暫停時(shí)間和吞吐量之間平衡從而產(chǎn)生的 GC。關(guān)注公眾號(hào):碼猿技術(shù)專欄,回復(fù)關(guān)鍵詞 1111 獲取阿里內(nèi)部 java 性能調(diào)優(yōu)手冊;目的是使得虛擬機(jī)性能更好的一種做法。
由此可見,通過可視化的工具,可以快速的幫我們分析 GC 的日志。我們得善于利用工具。
因?yàn)?gc 的日志文件,內(nèi)容太多,都是密密麻麻的數(shù)字,文本。看得實(shí)在是頭疼。
有了gc easy可視化工具,而且還是在線的,十分的方便。GC 日志分析是免費(fèi)的
2.4K star,一個(gè)高性能、無侵入的Java性能監(jiān)控和統(tǒng)計(jì)工具,有點(diǎn)東西!
Java 反射慢?它到底慢在哪?
Spring Boot 3 步完成日志脫敏,簡單實(shí)用~
關(guān)注公眾號(hào)【Java技術(shù)江湖】后回復(fù)“PDF”即可領(lǐng)取200+頁的《Java工程師面試指南》
強(qiáng)烈推薦,幾乎涵蓋所有Java工程師必知必會(huì)的知識(shí)點(diǎn),不管是復(fù)習(xí)還是面試,都很實(shí)用。




