關於虛擬機器記憶體和JVM記憶體設定的思考
背景
最近有同事總問JVM的設定問題.
之前總結過不少. 但是感覺沒法講對方說服
當然了, 自己能力有限, 只能自說自話.
現在這個就是留存一個底稿. 希望能人能幫忙解釋
關於記憶體和CPU的觀點
CPU的能力有上限. 一般情況下不建議讓CPU處於高峰作業.
尤其是 x86 的機器 有超執行緒, 其實CPU顯示使用率超過一半的CPU時就已經快到上限了.
所以機器的效能 40%左右的CPU 可能相應時間和吞吐量都比較好一些.
超過 50% 超執行緒中的 另外一個執行緒開始爭搶暫存器, 效能就會下降.
但是CPU使用率有非常依賴記憶體的使用情況.
記憶體高, 不需要swap,不需要gc, 不需要執行緒切換換入換出. CPU的壓力就小
會主要進行 業務操作的CPU. 但是如果機器記憶體不專一, 會導致 sys cpu升高
如果JVM設定的不合理, 經常fullgc 會倒是 user cpu升高.
CPU效能好的機器 可以容忍, 但是如果CPU效能不好,尤其是訪存和記憶體頻寬不高的機器.
此時就會導致非常嚴重的效能問題.
所以提升效能的本質, 就是減少記憶體的非主觀操作, 減少FullGC,減少記憶體被動的換入換出.
關於信創的思考
現在國家隊信創的要求主要在於CPU
磁碟和記憶體可以選用非國產的裝置.
因為國產化是一個持久化的過程,現階段來看,
國產CPU的效能距離Intel最新的CPU還有5-8年的差距.
此時揚長避短就是一個很必要的措施.
增加記憶體, 減少CPU的工作, 避免swap,換入換出,fullGC
透過減少CPU的壓力來提高響應效率和增加吞吐量.
高速的CPU,高速的記憶體能夠減少記憶體latch,lock以及資料庫的lock
的持有時間, 能夠減少lock wait, 減少死鎖出現的機率
提高產品執行效率, 降低響應時間. 提高客戶體驗.
國產化的虛擬機器系統可能會增加一些安全審計相關的元件. 會都佔用部分記憶體
虛擬機器的記憶體要至少大於 jvm的堆區 10G 左右才比較安全.
因為根據之前的經驗. 系統會佔用 1-2G的記憶體.
非堆區佔用 4G 左右的記憶體
因為讀寫檔案會佔用buffer和cache, 也會佔用2G左右的記憶體.
還要空餘一部分記憶體應對突發的響應.
如果記憶體不足,導致使用到了swap, 效能會指數級的下降.
所以最開始的K8S 都要求關閉swap, 出現記憶體不夠, 立即OOM
透過fast failure 的方式來避免 應用卡頓, 產生不可控的事件.
一個現象
公司裡面一堆機器在跑自動化.
其中有一臺機器的FullGC特別高
發現 這個機器的 xmx的設定不合理,跟其他機器不一樣.
稍微大一點的記憶體能夠極大的減少fullGC的次數.
記憶體配置與執行情況
宿主機的監控情況為:
PID USER VIRT RES SHR S %CPU %MEM TIME+ COMMAND
8422 root 19.5g 14.1g 144680 S 771.5 29.8 2843:18 java
jvm的記憶體配置為: 10G
-XX:InitialHeapSize=10737418240 -XX:MaxHeapSize=10737418240
然後發現jvm在瘋狂GC
圖示
說明
圖中很明顯可以看到 除了這一個之外, 其他額FullGC次數非常少.
這麼多fullGC 跟記憶體大小不太足有很大的關係.
dump分析
time jmap -dump:live,format=b,file=/20240412.hprof 8422
記憶體使用情況
記憶體使用分析
最多的幾個記憶體區域
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
org.springframework.data.jpa.mapping.JpaMetamodelMappingContext
org.hibernate.metamodel.internal.MetamodelImpl
org.hibernate.service.internal.SessionFactoryServiceRegistryImpl
這些基本上佔用了接近2G的記憶體空間.
理論上這些應該都是必須的
剩下 5G的空間是業務程式碼邏輯使用
所以就太小了.
關於記憶體佔用的分析
堆區設定 10G
top顯示記憶體 14.1G
多與的 4.1G 是非堆區.
比較關鍵的資料應該是包含:
1. 執行緒棧區
2. 後設資料區(包含方法區,也就是類的後設資料)
3. 直接記憶體
4. 本地記憶體
5. gc,jit等c語言編寫的工具使用的記憶體區域.
一般情況下 top 使用的記憶體減去 xms=xmx=堆區 記憶體的情況
就應該是比較準確的非堆區整體用量.
記憶體不足時CPU使用增加
整個java使用的時間為:
2-05:11:50
合計為: 3200分鐘左右
計算如下的 FullGC的總時間為: 2000分鐘
換句話說, FullGC/ALL 時間的比率為: 63%
所以機器其實一直在瘋狂的出工, 但是出的力就比較少.
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
8453 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 253:24.53 java
8454 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 253:11.33 java
8451 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 253:05.68 java
8455 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 253:01.91 java
8452 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 252:55.15 java
8449 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 252:54.04 java
8450 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 252:53.27 java
8448 root 20 0 19.1g 14.0g 144712 S 0.0 29.7 252:52.40 java
相同的一個設定為 24G堆區的機器
CPU總時間為:
root 23429 23412 99 4月11 ? 1-05:09:28
1750 分鐘左右.
檢視fullGC相關的時間 累計才 20分鐘. 這樣的話 大部分業務時間就對應的上了.
23450 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:18.84 java
23451 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:18.81 java
23452 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:18.97 java
23453 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:19.43 java
23454 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:17.78 java
23455 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:18.71 java
23456 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:19.03 java
23457 root 20 0 35.5g 30.1g 24332 S 0.0 63.8 2:18.52 java
結論
相同的CPU在不同記憶體配置的情況下使用的CPU時間有一倍的差距
其中差異的大部分都被因為堆區不太夠導致的fullGC消耗的時間來產生.
所以增加堆區, 到一小時不超過一次FullGC的場景下.
機器的效能會比較好一些.
業務高峰期應該建議不要有fullGC的不然會導致卡頓.
如果業務量比較大的FullGC可能至少會產生 10秒-30秒的等待
會導致客戶體驗明顯下降.