面試官:今天要不來聊聊JVM調優相關的吧?
面試官:你曾經在生產環境下有過調優JVM的經歷嗎?
候選者:沒有
面試官:...
候選者:嗯...是這樣的,我們一般優化系統的思路是這樣的
候選者:1. 一般來說關係型資料庫是先到瓶頸,首先排查是否為資料庫的問題
候選者:(這個過程中就需要評估自己建的索引是否合理、是否需要引入分散式快取、是否需要分庫分表等等)
候選者:2. 然後,我們會考慮是否需要擴容(橫向和縱向都會考慮)
候選者:(這個過程中我們會懷疑是系統的壓力過大或者是系統的硬體能力不足導致系統頻繁出現問題)
候選者:3. 接著,應用程式碼層面上排查並優化
候選者:(擴容是不能無止境的,裡頭裡外都是錢阿。這個過程中我們會審視自己寫的程式碼是否存在資源浪費的問題,又或者是在邏輯上可存在優化的地方,比如說通過並行的方式處理某些請求)
候選者:4. 再接著,JVM層面上排查並優化
候選者:(審視完程式碼之後,這個過程我們觀察JVM是否存在多次GC問題等等)
候選者:5. 最後,網路和作業系統層面排查
候選者:(這個過程檢視記憶體/CPU/網路/硬碟讀寫指標是否正常等等)
候選者:絕大多數情況下,到第三步就結束了,一般經過「運維團隊」給我們設定的JVM和機器上的引數,已經滿足絕大多數的需求了。
候選者:之前有過其他團隊在「大促」發現介面處理超時的問題,那時候查各種監控懷疑是FULL GC導致的
候選者:第一想法不是說去調節各種JVM引數來進行優化,而是直接加機器
候選者:(用最粗暴的方法,解決問題是最簡單的,擴容YYDS)
面試官:確實
候選者:不過,我是學過JVM相關的調優命令和思路的。
候選者:在我的理解下,調優JVM其實就是在「理解」JVM記憶體結構以及各種垃圾收集器前提下,結合自己的現有的業務來「調整引數」,使自己的應用能夠正常穩定執行。
候選者:一般調優JVM我們認為會有幾種指標可以參考:『吞吐量』、『停頓時間』和『垃圾回收頻率』
候選者:基於這些指標,我們就有可能需要調整:
候選者:1. 記憶體區域大小以及相關策略(比如整塊堆記憶體佔多少、新生代佔多少、老年代佔多少、Survivor佔多少、晉升老年代的條件等等)
候選者:比如(-Xmx:設定堆的最大值、-Xms:設定堆的初始值、-Xmn:表示年輕代的大小、-XX:SurvivorRatio:伊甸區和倖存區的比例等等)
候選者:(按經驗來說:IO密集型的可以稍微把「年輕代」空間加大些,因為大多數物件都是在年輕代就會滅亡。記憶體計算密集型的可以稍微把「老年代」空間加大些,物件存活時間會更長些)
候選者:2. 垃圾回收器(選擇合適的垃圾回收器,以及各個垃圾回收器的各種調優引數)
候選者:比如(-XX:+UseG1GC:指定 JVM 使用的垃圾回收器為 G1、-XX:MaxGCPauseMillis:設定目標停頓時間、-XX:InitiatingHeapOccupancyPercent:當整個堆記憶體使用達到一定比例,全域性併發標記階段 就會被啟動等等)
候選者:沒錯,這些都是因地制宜,具體問題具體分析(前提是得懂JVM的各種基礎知識,基礎知識都不懂,談何調優)
候選者:在大多數場景下,JVM 已經能夠達到「開箱即用」
面試官:確實
候選者:一般我們是「遇到問題」之後才進行調優的,而遇到問題後需要利用各種的「工具」進行排查
候選者:1. 通過jps命令檢視Java程式「基礎」資訊(程式號、主類)。這個命令很常用的就是用來看當前伺服器有多少Java程式在執行,它們的程式號和載入主類是啥
候選者:2. 通過jstat命令檢視Java程式「統計類」相關的資訊(類載入、編譯相關資訊統計,各個記憶體區域GC概況和統計)。這個命令很常用於看GC的情況
候選者:3. 通過jinfo命令來檢視和調整Java程式的「執行引數」。
候選者:4. 通過jmap命令來檢視Java程式的「記憶體資訊」。這個命令很常用於把JVM記憶體資訊dump到檔案,然後再用MAT( Memory Analyzer tool 記憶體解析工具)把檔案進行分析
候選者:5. 通過jstack命令來檢視JVM「執行緒資訊」。這個命令用常用語排查死鎖相關的問題
候選者:6. 還有近期比較熱門的Arthas(阿里開源的診斷工具),涵蓋了上面很多命令的功能且自帶圖形化介面。這也是我這邊常用的排查和分析工具
面試官:嗯...好吧。之前聊JVM的時候,你也提到過在「解釋」階段,會有兩種方式把位元組碼資訊解釋成機器指令碼,一個是位元組碼直譯器、一個是即時編譯器(JIT)
面試官:我想問問,你瞭解JVM的JIT優化技術嘛?
候選者:JIT優化技術比較出名的有兩種:方法內聯和逃逸分析
候選者:所謂方法內聯就是把「目標方法」的程式碼複製到「呼叫的方法」中,避免發生真實的方法呼叫
候選者:因為每次方法呼叫都會生成棧幀(壓棧出棧記錄方法呼叫位置等等)會帶來一定的效能損耗,所以「方法內聯」的優化可以提高一定的效能
候選者:在JVM中也有相關的引數給予我們指定(-XX:MaxFreqInlineSize、-XX:MaxInlineSize)
候選者:而「逃逸分析」則是判斷一個物件是否被外部方法引用或外部執行緒訪問的分析技術,如果「沒有被引用」,就可以對其進行優化,比如說:
候選者:1. 鎖消除(同步忽略):該物件只在方法內部被訪問,不會被別的地方引用,那麼就一定是執行緒安全的,可以把鎖相關的程式碼給忽略掉
候選者:2. 棧上分配:該物件只會在方法內部被訪問,直接將物件分配在「棧」中(Java預設是將物件分配在「堆」中,是需要通過JVM垃圾回收期進行回收,需要損耗一定的效能,而棧內分配則快很多)
候選者:3. 標量替換/分離物件:當程式真正執行的時候可以不建立這個物件,而直接建立它的成員變數來代替。將物件拆分後,可以分配物件的成員變數在棧或暫存器上,原本的物件就無需分配記憶體空間了
候選者:不過扯了這麼多,不同的JVM版本對JIT的優化都不太相同(:這裡也只能算是一個參考
面試官:懂了。
建議閱讀資料:【美團技術部落格】Java中9種常見的CMS GC問題分析與解決
歡迎關注我的微信公眾號【Java3y】來聊聊Java面試,對線面試官系列持續更新中!
【對線面試官-移動端】系列 一週兩篇持續更新中!
【對線面試官-電腦端】系列 一週兩篇持續更新中!
原創不易!!求三連!!