高效應(yīng)用程序應(yīng)該配置的七個JVM參數(shù)?
圖片圍繞垃圾收集和內(nèi)存,您可以將 600 多個參數(shù)傳遞給 JVM。如果包括其他方面 JVM 參數(shù)計數(shù)將輕松超過 1000+。爭論點(diǎn)太多,任何人都無法消化和理解。在本文中,我們將重點(diǎn)介紹七個重要的 JVM 參數(shù),您可能會發(fā)現(xiàn)它們很有用。
1. -Xmx 和 -XX:MaxMetaspaceSize
-Xmx 可能是最重要的 JVM 參數(shù)。-Xmx 定義您分配給應(yīng)用程序的最大堆大小。您可以像這樣定義應(yīng)用程序的堆大小:
-Xmx2g
這帶來了一個問題,我的應(yīng)用程序的正確堆大小是多少?我應(yīng)該為我的應(yīng)用程序分配大堆大小還是小堆大???答案是:這取決于你的服務(wù)在承載預(yù)期流量時需要多少內(nèi)存,你可以通過壓測或者實(shí)際線上流量獲得。
元空間是 JVM 的元數(shù)據(jù)定義(例如類定義、方法定義)將被存儲的區(qū)域。默認(rèn)情況下,可用于存儲此元數(shù)據(jù)信息的內(nèi)存量是無限的(即受容器或機(jī)器的 RAM 大小限制)。您需要使用 -XX:MaxMetaspaceSize 參數(shù)來指定可用于存儲元數(shù)據(jù)信息的內(nèi)存量的上限。
-XX:MaxMetaspaceSize=256m
2. GC算法
截至目前 OpenJDK 中有 7 種不同的 GC 算法:
- Serial GC
- Parallel GC
- Concurrent Mark & Sweep GC
- G1 GC
- Shenandoah GC
- ZGC
- Epsilon GC
如果您沒有明確指定 GC 算法,那么 JVM 將選擇默認(rèn)算法。在 Java 8 之前,Parallel GC 是默認(rèn)的 GC 算法。從 Java 9 開始,G1 GC 是默認(rèn)的 GC 算法。
GC 算法的選擇在確定應(yīng)用程序的性能方面起著至關(guān)重要的作用。根據(jù)我們的研究,我們正在觀察 ZGC 算法的出色性能結(jié)果。如果您使用 JVM 11+ 運(yùn)行,那么您可以考慮使用 ZGC 算法(即 -XX:+UseZGC)。
3. 啟用GC日志
垃圾收集日志包含有關(guān)垃圾收集事件、內(nèi)存回收、暫停時間持續(xù)時間的信息……您可以通過傳遞以下 JVM 參數(shù)來啟用垃圾收集日志:
從 JDK 1 到 JDK 8:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:{file-path}
從 JDK 9 及更高版本:
-Xlog:gc*:file={file-path}
例子:
-XX:+PrintGCDetails-XX:+PrintGCDateStamps-Xloggc:/opt/workspace/myAppgc.log
-Xlog:gc*:file=/opt/workspace/myAppgc.log
通常 GC 日志用于調(diào)整垃圾收集性能。但是,GC 日志包含重要的微觀指標(biāo)。這些指標(biāo)可用于預(yù)測應(yīng)用程序的可用性和性能特征。GC 吞吐量是您的應(yīng)用程序花在處理客戶事務(wù)上的時間與花在處理 GC 活動上的時間的比值。假設(shè)您的應(yīng)用程序的 GC 吞吐量為 98%,那么這意味著應(yīng)用程序?qū)?98% 的時間用于處理客戶活動,其余 2% 用于 GC 活動。
現(xiàn)在讓我們看一下健康 JVM 的堆使用圖:
圖片您可以看到完美的鋸齒圖案。您可以注意到,當(dāng) Full GC(紅色三角形)運(yùn)行時,內(nèi)存利用率一直下降到底部。
現(xiàn)在讓我們看一下有問題 JVM 的堆使用圖:
您可以注意到圖表的右端,即使 GC 反復(fù)運(yùn)行,內(nèi)存利用率也沒有下降。這是應(yīng)用程序遇到某種內(nèi)存問題的典型跡象。
如果您仔細(xì)查看圖表,您會注意到重復(fù)的完整 GC 開始發(fā)生在上午 8 點(diǎn)左右。但是,應(yīng)用程序僅在上午 8:45 左右才開始出現(xiàn) OutOfMemoryError。到上午 8 點(diǎn),應(yīng)用程序的 GC 吞吐量約為 99%。但就在早上 8 點(diǎn)之后,GC 吞吐量開始下降到 60%。因?yàn)楫?dāng)重復(fù) GC 運(yùn)行時,應(yīng)用程序不會處理任何客戶事務(wù),它只會執(zhí)行 GC 活動。作為一種主動措施,如果您發(fā)現(xiàn) GC 吞吐量開始下降,您可以從負(fù)載均衡服務(wù)器中剔除該 JVM。這樣不健康的 JVM 就不會處理任何新的流量。它將最大限度地減少對客戶的影響。
重復(fù)的 Full GC 發(fā)生在 OutOfMemoryError 之前
4. -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath
OutOfMemoryError 是一個嚴(yán)重的問題,會影響應(yīng)用程序的可用性/性能 SLA。要診斷 OutOfMemoryError 或任何與內(nèi)存相關(guān)的問題,必須在應(yīng)用程序開始遇到 OutOfMemoryError 之前的那一刻或幾分鐘捕獲堆轉(zhuǎn)儲。由于我們不知道什么時候會拋出 OutOfMemoryError,因此很難在拋出的時候手動捕獲堆轉(zhuǎn)儲。但是,可以通過傳遞以下 JVM 參數(shù)來自動捕獲堆轉(zhuǎn)儲:
-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath={HEAP-DUMP-FILE-PATH}
在“-XX:HeapDumpPath”中,您需要指定應(yīng)該存儲堆轉(zhuǎn)儲的文件路徑。當(dāng)您傳遞這兩個 JVM 參數(shù)時,當(dāng)拋出 OutOfMemoryError 時,堆轉(zhuǎn)儲將被自動捕獲并寫入定義的文件路徑。
例子:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/crashes/my-heap-dump.hprof
捕獲堆轉(zhuǎn)儲后,您可以使用 HeapHero、EclipseMAT等工具來分析堆轉(zhuǎn)儲。
5. -Xss
每個應(yīng)用程序?qū)⒂袛?shù)十、數(shù)百、數(shù)千個線程。每個線程都有自己的堆棧。在每個線程的堆棧中存儲以下信息:
- 當(dāng)前執(zhí)行的方法/函數(shù)
- 原始數(shù)據(jù)類型
- 變量
- 對象指針
- 返回值。
它們中的每一個都消耗內(nèi)存。如果它們的消耗超過一定的限制,則拋出 StackOverflowError。但您可以通過傳遞 -Xss 參數(shù)來增加線程的堆棧大小限制。
例子:
-Xss256k
如果將此 -Xss 值設(shè)置為一個巨大的數(shù)字,那么內(nèi)存將被阻塞和浪費(fèi)。假設(shè)您將 -Xss 值分配為 2mb,而它只需要 256kb,那么您最終會浪費(fèi)大量內(nèi)存,而不僅僅是 1792kb(即 2mb – 256kb)。你想知道為什么嗎?
假設(shè)您的應(yīng)用程序有 500 個線程,然后 -Xss 值為 2mb,您的線程將消耗 1000mb 內(nèi)存(即 500 個線程 x 2mb/線程)。另一方面,如果您僅將 -Xss 分配為 256kb,那么您的線程將僅消耗 125mb 的內(nèi)存(即 500 個線程 x 256kb/線程)。您將為每個 JVM 節(jié)省 875mb(即 1000mb – 125mb)的內(nèi)存。是的,它會產(chǎn)生如此巨大的變化。
我們的建議是從較低的值(比如 256kb)開始。使用此設(shè)置運(yùn)行徹底的回歸、性能和 AB 測試。僅當(dāng)您遇到 StackOverflowError 時才增加該值,否則請考慮堅(jiān)持較低的值。
6. -Dsun.net.client.defaultConnectTimeout 和 -Dsun.net.client.defaultReadTimeout
現(xiàn)代應(yīng)用程序使用多種協(xié)議(即 SOAP、REST、HTTP、HTTPS、JDBC、RMI...)來連接遠(yuǎn)程應(yīng)用程序。有時遠(yuǎn)程應(yīng)用程序可能需要很長時間才能響應(yīng)。有時它可能根本沒有反應(yīng)。
如果您沒有適當(dāng)?shù)某瑫r設(shè)置,并且遠(yuǎn)程應(yīng)用程序響應(yīng)速度不夠快,那么您的應(yīng)用程序線程/資源將被卡住。遠(yuǎn)程應(yīng)用程序無響應(yīng)會影響應(yīng)用程序的可用性。它可以使您的應(yīng)用程序陷入停頓。為了保護(hù)您的應(yīng)用程序的高可用性,應(yīng)配置適當(dāng)?shù)某瑫r設(shè)置。
您可以在 JVM 級別傳遞這兩個強(qiáng)大的超時網(wǎng)絡(luò)屬性,這些屬性可以全局適用于所有使用 java.net.URLConnection 的協(xié)議處理程序:
sun.net.client.defaultConnectTimeout 指定與主機(jī)建立連接的超時時間(以毫秒為單位)。例如,對于 HTTP 連接,它是與 HTTP 服務(wù)器建立連接時的超時。sun.net.client.defaultReadTimeout 指定與資源建立連接時從輸入流中讀取的超時時間(以毫秒為單位)。例如,如果您想將這些屬性設(shè)置為 2 秒:
-Dsun.net.client.defaultConnectTimeout=2000
-Dsun.net.client.defaultReadTimeout=2000
請注意,這兩個屬性的默認(rèn)值為 -1,這意味著沒有設(shè)置超時。
7. -Duser.timeZone
您的應(yīng)用程序可能對時間/日期有敏感的業(yè)務(wù)需求。例如,如果您正在構(gòu)建一個交易應(yīng)用程序,您不能在上午 9:30 之前進(jìn)行交易。要實(shí)現(xiàn)那些與時間/日期相關(guān)的業(yè)務(wù)需求,您可能會使用 java.util.Date、java.util.Calendar 對象。默認(rèn)情況下,這些對象從底層操作系統(tǒng)獲取時區(qū)信息。這將成為一個問題;如果您的應(yīng)用程序在分布式環(huán)境中運(yùn)行。請看以下場景:
- 如果您的應(yīng)用程序跨多個數(shù)據(jù)中心運(yùn)行,例如舊金山、芝加哥、新加坡,那么每個數(shù)據(jù)中心中的 JVM 最終將具有不同的時區(qū)。因此,每個數(shù)據(jù)中心的 JVM 會表現(xiàn)出不同的行為。這將導(dǎo)致不一致的結(jié)果。
- 如果您在云環(huán)境中部署應(yīng)用程序,應(yīng)用程序可能會在您不知情的情況下移動到不同的數(shù)據(jù)中心。同樣在這種情況下,您的應(yīng)用程序最終會產(chǎn)生不同的結(jié)果。
- 您自己的運(yùn)營團(tuán)隊(duì)也可以在不告知開發(fā)團(tuán)隊(duì)知識的情況下更改時區(qū)。它還會扭曲結(jié)果。為了避免這些混亂,強(qiáng)烈建議使用 -Duser.timezone 系統(tǒng)屬性在 JVM 上設(shè)置時區(qū)。例如,如果您想為您的應(yīng)用程序設(shè)置 EDT 時區(qū),您將執(zhí)行以下操作:
-Duser.timezone=US/Eastern