前言
面試官:在工作中做過 JVM 調(diào)優(yōu)嗎?講講做過哪些 JVM 調(diào)優(yōu)?
我一個QPS不到10的項目,上次問我緩存穿透緩存雪崩,這次問我 JVM 調(diào)優(yōu),我是真滴難。
不過大家別慌,熱心的我給大家找來了幾個滿分回答,大家選擇合適的使用。
回答1:聽好了,下面將是我第一次 JVM 調(diào)優(yōu)。
回答2:我一般面試的時候才調(diào)優(yōu)。
回答3:我一般直接加機器、加內(nèi)存。
回答4:老子直接用的 ZGC,調(diào)個蛇皮。
正文
1、JVM 究竟需不需要調(diào)優(yōu)?
JVM 經(jīng)過這么多年的發(fā)展和驗證,整體是非常健壯的。個人認為99%的情況下,基本用不到 JVM 調(diào)優(yōu)。
通常來說,我們的 JVM 參數(shù)配置大多還是會遵循 JVM 官方的建議,例如:
-
-XX:NewRatio=2,年輕代:老年代=1:2
-
-XX:SurvivorRatio=8,eden:survivor=8:1
-
堆內(nèi)存設(shè)置為物理內(nèi)存的3/4左右
-
等等
JVM 參數(shù)的默認(推薦)值都是經(jīng)過 JVM 團隊的反復測試和前人的充分驗證得出的比較合理的值,因此通常來說是比較靠譜和通用的,一般不會出大問題。
當然,更重要的是,大部分的應用 QPS 都不到10,數(shù)據(jù)量不到幾萬,這種低壓環(huán)境下,想讓 JVM 出問題,說實話也挺難的。
大部分同學更常遇到的應該是自己的代碼 bug 導致 OOM、CPU load高、GC頻繁啥的,這些場景也基本都是代碼修復即可,通常不需要動 JVM。
當然,俗話說得好,凡事無絕對,還是有一小部分場景,是可能需要用到 JVM 調(diào)優(yōu)的。具體哪些場景,我們在下面介紹。
值得一提的是,我們這邊所說的 JVM 調(diào)優(yōu)更多的是針對自己的業(yè)務場景對 JVM 參數(shù)進行優(yōu)化調(diào)整,使其更適合我們的業(yè)務,而不是指對 JVM 源碼的改動。
2、JVM 調(diào)優(yōu)沒有什么必要,使用性能更好的垃圾回收器就能解決問題了?
這是我在網(wǎng)上看到的一個說法,因為贊同的人比較多,我估計有不少同學也會有這個想法,因此在這邊談下自己的看法。
1)實戰(zhàn)角度
不考慮應付面試的因素,升級垃圾回收器確實會是最有效的方式之一,例如:CMS 升級到 G1,甚至 ZGC。
這個很容易理解,更高版本的垃圾回收器相當于是 JVM 開發(fā)人員對 JVM 做的優(yōu)化,人家畢竟是專門做這個的,所以通常來說升級高版本的性能會有不少的提升。
G1 目前已經(jīng)有開始在逐漸應用開來,周圍有不少團隊在 JDK8 中使用了 G1,就我了解到的,還是存在不少問題的,不少同學在不斷進行參數(shù)的調(diào)整,而在 JDK11 中能優(yōu)化成啥樣還有待驗證。
ZGC 目前應用的還比較少,僅從對外公布的數(shù)據(jù)來看很好看,最大暫停時間不超過10ms,甚至是1ms,大家都抱有很高的期望。但是從目前我收集到的一些資料來看,ZGC 也并不是銀彈,已知的明顯問題有:
-
吞吐量相較于 G1?會有所下降,官方稱最大不超過15%
-
ZGC如果遇到非常高的對象分配速率(allocation rate)的話會跟不上,目前唯一有效的“調(diào)優(yōu)”方式就是增大整個GC堆的大小來讓ZGC有更大的喘息空間——R大與ZGC領(lǐng)隊溝通后的原話
而且,隨著后續(xù) ZGC 應用開來,后續(xù)一定會不斷出現(xiàn)更多問題的。
整體而言,個人覺得 JVM 調(diào)優(yōu)在某些場景下還是有必要的,畢竟有句話叫:沒有最好的,只有最合適的。
2)面試角度
如果你回答直接升級垃圾收集器,面試官可能也贊同,但是這個話題可能就這樣結(jié)束了,面試官大概率沒聽到他想要的回答,你在這題的肯定拿不到加分,甚至可能會被扣分。
所以,在面試的時候,你可以回答升級垃圾收集器,但是你不能只回答升級垃圾收集器。
3、JVM 何時優(yōu)化?
忌過早優(yōu)化?!队嬎銠C程序設(shè)計藝術(shù)》的作者高德納(Donald Ervin Knuth)曾說過一句經(jīng)典的話:
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.
真正的問題是,程序猿在錯誤的地方和錯誤的時間花了太多的時間擔心效率問題;過早的優(yōu)化是編程中所有(或者至少是大部分)罪惡的根源。
忌過早并不是說就完全不管,比較正確的做法應該是給核心服務的一些重要?JVM?指標配上監(jiān)控告警,當指標出現(xiàn)波動或者異常時,能及時介入排查。
面試官:JVM 有哪些核心指標?合理范圍應該是多少?
這個問題沒有統(tǒng)一的答案,因為每個服務對AVG/TP999/TP9999等性能指標的要求是不同的,因此合理的范圍也不同。
為了防止面試官追問,對于普通的 Java 后端應用來說,我這邊給出一份相對合理的范圍值。以下指標都是對于單臺服務器來說:
-
jvm.gc.time:每分鐘的GC耗時在1s以內(nèi),500ms以內(nèi)尤佳
-
jvm.gc.meantime:每次YGC耗時在100ms以內(nèi),50ms以內(nèi)尤佳
-
jvm.fullgc.count:FGC最多幾小時1次,1天不到1次尤佳
-
jvm.fullgc.time:每次FGC耗時在1s以內(nèi),500ms以內(nèi)尤佳
通常來說,只要這幾個指標正常,其他的一般不會有問題,如果其他地方出了問題,一般都會影響到這幾個指標。
4、JVM 優(yōu)化步驟?
4.1、分析和定位當前系統(tǒng)的瓶頸
對于JVM的核心指標,我們的關(guān)注點和常用工具如下:
1)CPU指標
-
查看占用CPU最多的進程
-
查看占用CPU最多的線程
-
查看線程堆??煺招畔?/p>
-
分析代碼執(zhí)行熱點
-
查看哪個代碼占用CPU執(zhí)行時間最長
-
查看每個方法占用CPU時間比例
常見的命令:
// 顯示系統(tǒng)各個進程的資源使用情況
top
// 查看某個進程中的線程占用情況
top -Hp pid
// 查看當前 Java 進程的線程堆棧信息
jstack pid
常見的工具:JProfiler、JVM Profiler、Arthas等。
2)JVM 內(nèi)存指標
-
查看當前 JVM 堆內(nèi)存參數(shù)配置是否合理
-
查看堆中對象的統(tǒng)計信息
-
查看堆存儲快照,分析內(nèi)存的占用情況
-
查看堆各區(qū)域的內(nèi)存增長是否正常
-
查看是哪個區(qū)域?qū)е碌腉C
-
查看GC后能否正?;厥盏絻?nèi)存
常見的命令:
// 查看當前的 JVM 參數(shù)配置
ps -ef | grep java
// 查看 Java 進程的配置信息,包括系統(tǒng)屬性和JVM命令行標志
jinfo pid
// 輸出 Java 進程當前的 gc 情況
jstat -gc pid
// 輸出 Java 堆詳細信息
jmap -heap pid
// 顯示堆中對象的統(tǒng)計信息
jmap -histo:live pid
// 生成 Java 堆存儲快照dump文件
jmap -F -dump:format=b,file=dumpFile.phrof pid
常見的工具:Eclipse MAT、JConsole等。
3)JVM GC指標
-
查看每分鐘GC時間是否正常
-
查看每分鐘YGC次數(shù)是否正常
-
查看FGC次數(shù)是否正常
-
查看單次FGC時間是否正常
-
查看單次GC各階段詳細耗時,找到耗時嚴重的階段
-
查看對象的動態(tài)晉升年齡是否正常
JVM 的 GC指標一般是從 GC 日志里面查看,默認的 GC 日志可能比較少,我們可以添加以下參數(shù),來豐富我們的GC日志輸出,方便我們定位問題。
GC日志常用 JVM 參數(shù):
// 打印GC的詳細信息
-XX:+PrintGCDetails
// 打印GC的時間戳
-XX:+PrintGCDateStamps
// 在GC前后打印堆信息
-XX:+PrintHeapAtGC
// 打印Survivor區(qū)中各個年齡段的對象的分布信息
-XX:+PrintTenuringDistribution
// JVM啟動時輸出所有參數(shù)值,方便查看參數(shù)是否被覆蓋
-XX:+PrintFlagsFinal
// 打印GC時應用程序的停止時間
-XX:+PrintGCApplicationStoppedTime
// 打印在GC期間處理引用對象的時間(僅在PrintGCDetails時啟用)
-XX:+PrintReferenceGC
以上就是我們定位系統(tǒng)瓶頸的常用手段,大部分問題通過以上方式都能定位出問題原因,然后結(jié)合代碼去找到問題根源。
4.2、確定優(yōu)化目標
定位出系統(tǒng)瓶頸后,在優(yōu)化前先制定好優(yōu)化的目標是什么,例如:
-
將FGC次數(shù)從每小時1次,降低到1天1次
-
將每分鐘的GC耗時從3s降低到500ms
-
將每次FGC耗時從5s降低到1s以內(nèi)
-
...
4.3、制訂優(yōu)化方案
針對定位出的系統(tǒng)瓶頸制定相應的優(yōu)化方案,常見的有:
-
代碼bug:升級修復bug。典型的有:死循環(huán)、使用無界隊列。
-
不合理的JVM參數(shù)配置:優(yōu)化 JVM 參數(shù)配置。典型的有:年輕代內(nèi)存配置過小、堆內(nèi)存配置過小、元空間配置過小。
4.4、對比優(yōu)化前后的指標,統(tǒng)計優(yōu)化效果
4.5、持續(xù)觀察和跟蹤優(yōu)化效果
4.6、如果還需要的話,重復以上步驟
5、調(diào)優(yōu)案例:metaspace導致頻繁FGC問題
以下案例來源于網(wǎng)絡或本人真實經(jīng)驗,皆能自圓其說,理解掌握后同學們皆可拿來與面試官對線。
服務環(huán)境:ParNew +?CMS + JDK8
問題現(xiàn)象:服務頻繁出現(xiàn)FGC
原因分析:
1)首先查看GC日志,發(fā)現(xiàn)出現(xiàn)FGC的原因是metaspace空間不夠
對應GC日志:
Full GC (Metadata GC Threshold)
2)進一步查看日志發(fā)現(xiàn)元空間存在內(nèi)存碎片化現(xiàn)象
對應GC日志:
Metaspace ? ? ? used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
這邊簡單解釋下這幾個參數(shù)的意義
-
used :已使用的空間大小
-
capacity:當前已經(jīng)分配且未釋放的空間容量大小
-
committed:當前已經(jīng)分配的空間大小
-
reserved:預留的空間大小
這邊?used 比較容易理解,reserved 在本例不重要可以先忽略,主要是 capacity 和 committed 這2個容易搞混。
結(jié)合下圖來看更容易理解,元空間的分配以 chunk 為單位,當一個 ClassLoader 被垃圾回收時,所有屬于它的空間(chunk)被釋放,此時該 chunk 稱為 Free Chunk,而 committed chunk 就是 capacity chunk 和 free chunk 之和。
之所以說內(nèi)存存在碎片化現(xiàn)象就是根據(jù) used 和 capacity 的數(shù)據(jù)得來的,上面說了元空間的分配以 chunk 為單位,即使一個 ClassLoader 只加載1個類,也會獨占整個 chunk,所以當出現(xiàn) used 和 capacity 兩者之差較大的時候,說明此時存在內(nèi)存碎片化的情況。
GC日志demo如下:
{Heap before GC invocations=0 (full 0):
par new generation total 314560K, used 141123K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 50% used [0x00000000c0000000, 0x00000000c89d0d00, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 0K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
class space used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
1.448: [Full GC (Metadata GC Threshold) 1.448: [CMS: 0K->10221K(699072K), 0.0487207 secs] 141123K->10221K(1013632K), [Metaspace: 35337K->35337K(1099776K)], 0.0488547 secs] [Times: user=0.09 sys=0.00, real=0.05 secs]
Heap after GC invocations=1 (full 1):
par new generation total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 10221K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
class space used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
}
{Heap before GC invocations=1 (full 1):
par new generation total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 10221K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 35337K, capacity 56242K, committed 56320K, reserved 1099776K
class space used 4734K, capacity 8172K, committed 8172K, reserved 1048576K
1.497: [Full GC (Last ditch collection) 1.497: [CMS: 10221K->3565K(699072K), 0.0139783 secs] 10221K->3565K(1013632K), [Metaspace: 35337K->35337K(1099776K)], 0.0193983 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
Heap after GC invocations=2 (full 2):
par new generation total 314560K, used 0K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
eden space 279616K, 0% used [0x00000000c0000000, 0x00000000c0000000, 0x00000000d1110000)
from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
concurrent mark-sweep generation total 699072K, used 3565K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 17065K, capacity 22618K, committed 35840K, reserved 1079296K
class space used 1624K, capacity 2552K, committed 8172K, reserved 1048576K
}
元空間主要適用于存放類的相關(guān)信息,而存在內(nèi)存碎片化說明很可能創(chuàng)建了較多的類加載器,同時使用率較低。
因此,當元空間出現(xiàn)內(nèi)存碎片化時,我們會著重關(guān)注是不是創(chuàng)建了大量的類加載器。
3)通過 dump 堆存儲文件發(fā)現(xiàn)存在大量 DelegatingClassLoader
通過進一步分析,發(fā)現(xiàn)是由于反射導致創(chuàng)建大量?DelegatingClassLoader。其核心原理如下:
在 JVM 上,最初是通過 JNI 調(diào)用來實現(xiàn)方法的反射調(diào)用,當?JVM 注意到通過反射經(jīng)常訪問某個方法時,它將生成字節(jié)碼來執(zhí)行相同的操作,稱為膨脹(inflation)機制。如果使用字節(jié)碼的方式,則會為該方法生成一個 DelegatingClassLoader,如果存在大量方法經(jīng)常反射調(diào)用,則會導致創(chuàng)建大量 DelegatingClassLoader。
反射調(diào)用頻次達到多少才會從 JNI 轉(zhuǎn)字節(jié)碼?
默認是15次,可通過參數(shù) -Dsun.reflect.inflationThreshold 進行控制,在小于該次數(shù)時會使用 JNI 的方式對方法進行調(diào)用,如果調(diào)用次數(shù)超過該次數(shù)就會使用字節(jié)碼的方式生成方法調(diào)用。
分析結(jié)論:反射調(diào)用導致創(chuàng)建大量?DelegatingClassLoader,占用了較大的元空間內(nèi)存,同時存在內(nèi)存碎片化現(xiàn)象,導致元空間利用率不高,從而較快達到閾值,觸發(fā) FGC。
優(yōu)化策略:
1)適當調(diào)大?metaspace 的空間大小。
2)優(yōu)化不合理的反射調(diào)用。例如最常見的屬性拷貝工具類?BeanUtils.copyProperties 可以使用 mapstruct 替換。
總結(jié)
當被面試官問到 JVM 調(diào)優(yōu)時,完全可以按照本文的脈絡回答:
-
首先表態(tài)如果使用合理的 JVM 參數(shù)配置,在大多數(shù)情況應該是不需要調(diào)優(yōu)的——對應本文第1題
-
其次說明可能還是存在少量場景需要調(diào)優(yōu),我們可以對一些 JVM 核心指標配置監(jiān)控告警,當出現(xiàn)波動時人為介入分析評估——對應本文第3題
-
最后舉一個實際的調(diào)優(yōu)例子來加以說明——對應本文第5題
如果面試官反問怎么分析排查的,則可以使用本文第4題的常用命令和工具來與之對線。
這一套流程下來,我相信大部分面試官都會對你印象不錯。
?
最后
我是囧輝,一個堅持分享原創(chuàng)技術(shù)干貨的程序員,如果覺得本文對你有幫助,記得點贊關(guān)注,我們下期再見。
推薦閱讀
Java 基礎(chǔ)高頻面試題(2021年最新版)
Java 集合框架高頻面試題(2021年最新版)
面試必問的 Spring,你懂了嗎?文章來源:http://www.zghlxwxcb.cn/news/detail-806217.html
面試必問的 MySQL,你懂了嗎?文章來源地址http://www.zghlxwxcb.cn/news/detail-806217.html
到了這里,關(guān)于面試官:如何進行 JVM 調(diào)優(yōu)(附真實案例)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!