聽說 JVM 性能優(yōu)化很難?今天我小試了一把!
對于 Java 開發(fā)的同學來說,JVM 性能優(yōu)化可以說是比較難掌握的知識點。這不僅因為 JVM 性能優(yōu)化需要掌握晦澀難懂的 JVM 知識,還因為 JVM 性能優(yōu)化很難有使用場景。
這導致了許多人對 JVM 性能優(yōu)化不熟悉,感覺就像是空中樓閣的天物一樣不可觸及。這幾天工作中做了一次 JVM 性能優(yōu)化,我想這對于 JVM 調(diào)優(yōu)的初學者會有較大幫助。
背景
我們都知道 JVM 分為了新生代和老年代,并且我們在啟動應用的時候都會配置對應的參數(shù),為應用程序運行的 JVM 調(diào)整內(nèi)存大小。但我們都知道,很多時候我們都只是大致估計一個數(shù),隨便填填,然后就上線了。
作者所在的公司同樣存在這種情況,JVM 內(nèi)存大小基本上都設(shè)得挺大的,畢竟內(nèi)存大總比內(nèi)存溢出好,因此就造成了不少的內(nèi)存浪費。所以作者收到的任務就是對所有的應用進行一次排查,調(diào)整合適的內(nèi)存參數(shù),優(yōu)化 JVM 的性能。
調(diào)優(yōu)實戰(zhàn)
要對應用進行 JVM 性能調(diào)優(yōu),那么首先得知道其運行的情況。這就像去醫(yī)院看醫(yī)生,去開藥之前需要醫(yī)生先望聞問切一樣。在 Java 中,有很多方式可以觀察到 JVM 的內(nèi)部情況,例如 JDK 提供的各種命令工作。作者所在公司使用的是 Prometheus 進行監(jiān)控,因此我們可以直接在 Prometheus 上看到應用的 JVM 運行情況。
Prometheus 面板中與 JVM 相關(guān)的主要有四塊內(nèi)容:JVM Misc、JVM Memory Pools(Heap)、JVM Memory Pools(Non-Heap)、Garbage Collection。其中與我們此次較為相關(guān)的主要是:JVM Memory Pools(Heap)和 Garbage Collection。
JVM Memory Pools(Heap) 展示 JVM 堆內(nèi)存的使用情況,主要包括了新生代的 Survivor 區(qū)、Eden Space 區(qū)、老年代。
JVM Memory Pools(Heap)
Garbage Collection 展示 JVM 的垃圾回收情況,主要包括垃圾回收頻率(ops 表示一秒回收幾次,一般 0.5 是比較合理的值)、每次 GC 停頓時長(一般 80ms 以下是合理值)、分配到新生代/晉升老年代的內(nèi)存。
Garbage Collection
我們要進行 JVM 性能優(yōu)化,那么最簡單的一個方法就是觀察 Garbage Collection 的 GC 頻率以及停頓時間,我們大致就能判斷出應用的內(nèi)存利用效率。之后根據(jù)這兩個值的實際情況,將其調(diào)整到合理的范圍內(nèi),提高 JVM 的利用率。
如果一個應用的 GC 頻率只有 0.02,即每秒 GC 0.02 次,那么需要 50 秒才 GC 一次,那么其 GC 頻率是很低的。這時候很可能是分配了較大的新生代空間,這使得其很久才需要 GC 一次。這時候我們再看看其停頓時間,如果停頓時間也很短的話,那我們就可以判定該應用的內(nèi)存有優(yōu)化的空間。
在這種情況下,一般都是縮小分配的新生代的空間。新生代空間一旦變小了,那么其分配完的時間就會縮減。一旦空間被分配完,那么就會啟動進行 GC 操作。從而 GC 次數(shù)就會提升,提高應用的內(nèi)存利用率。
在進行內(nèi)存空間調(diào)整的時候,為了避免內(nèi)存劇烈波動導致的問題,一般我們都是小步快跑地一點點調(diào)整。先調(diào)整一點試一試,沒太大問題之后再調(diào)整到目標值。 畢竟是生產(chǎn)環(huán)境,要是出了什么岔子,那就得提桶跑路了,還是謹慎為好!
看到這里,想必大家應該也知道怎么做了。接下來無非就是調(diào)整 JVM 內(nèi)存空間的三個參數(shù)(-Xmx -Xms -Xmn),使 GC 頻率與 GC 停頓時間處于合理的區(qū)間。
應用層面優(yōu)化
除了 GC 頻率、GC 停頓時間,我們還能從應用的類型來分析 JVM 的內(nèi)存消耗情況。
例如對于接口類型的系統(tǒng)來說,很多請求都是 1 秒之內(nèi)就結(jié)束。對于這種類型的請求,他們進入應用時會分配內(nèi)存,結(jié)束時內(nèi)存就會立刻被回收,留存下來的對象很少。這種應用的 JVM 內(nèi)存情況大概是這樣的:新生代消耗比較大,并且隨著周期性回收內(nèi)存,但老年代的內(nèi)存消耗則更小。
對于那些持續(xù)性處理的應用,例如持續(xù)時間長的應用處理。因為其存活時間較久,所以可能會有更多的對象晉升到老年代,因此老年代的內(nèi)存消耗就比較大。
通過觀察 JVM 年輕代與老年代的內(nèi)存消耗情況,再結(jié)合應用本身的特性,我們可以發(fā)現(xiàn)應用中不合理的地方,再對應用進行針對性的優(yōu)化。例如:應用某個地方每次都會存儲大量的臨時數(shù)據(jù)到內(nèi)容中,這樣就造成了 JVM 可能爆發(fā) GC,從而導致應用卡頓。
總結(jié)
總結(jié)一下本篇文章的調(diào)優(yōu)方法:通過觀察 GC 頻率和停頓時間,來進行 JVM 內(nèi)存空間調(diào)整,使其達到最合理的狀態(tài)。調(diào)整過程記得小步快跑,避免內(nèi)存劇烈波動影響線上服務。 這其實是最為簡單的一種 JVM 性能調(diào)優(yōu)方式了,可以算是粗調(diào)吧。但 JVM 性能調(diào)優(yōu)還有更多、更詳細的參數(shù),后續(xù)有機會我們再聊聊。
此外,通過觀察 JVM 年輕代與老年代的情況,也可以幫助我們對應用進行針對性的優(yōu)化,從而提升應用本身的性能。
如果你之前沒了解過 JVM 的基礎(chǔ)理論知識,那么你可能看不懂這篇文章。那么我推薦你看看我的「JVM 基礎(chǔ)入門系列」,文章由淺入深、循序漸進,可以讓你對 JVM 有個感性的理解??赐曛笤賮砜催@篇文章,你肯定有種豁然開朗的感覺!
JVM 基礎(chǔ)入門系列傳送門:JVM 基礎(chǔ)入門系列
關(guān)于 JVM 性能調(diào)優(yōu),就分享到這里吧。
本文轉(zhuǎn)載自微信公眾號「陳樹義」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系陳樹義公眾號。