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