JVM的堆(Heap)佔用記憶體過大會引發如下問題:如果JVM直接運行在Linux系統,可能會導致Java進程被Linux系統的OOM Killer所終止(Kill);如果JVM運行在Docker容器環境,可能會表現為頻繁異常重啟。本文針對在容器環境下運行JVM的記憶體配置給出建議,並解決OOM(Out of Memory)的相關常見問題。
通過-XX:MaxRAMPercentage限制JVM使用容器記憶體的最大百分比(推薦)
容器環境下,推薦的JVM參數設定如下:
-XX:+UseContainerSupport -XX:InitialRAMPercentage=70.0 -XX:MaxRAMPercentage=70.0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof參數說明如下:
參數 | 說明 |
| 設定JVM檢測所處容器的記憶體大小和處理器數量,而不是檢測整個作業系統的。 JVM會使用上述檢測到的資訊進行資源分派,例如: |
| 設定JVM使用容器記憶體的初始百分比。建議與 |
| 設定JVM使用容器記憶體的最大百分比。由於存在系統組件開銷,建議最大不超過75.0,推薦設定為70.0,代表JVM最大使用容器記憶體的70%。 |
| 輸出GC詳細資料。 |
| 輸出GC時間戳記。日期形式,例如2019-12-24T21:53:59.234+0800。 |
| GC記錄檔路徑。需保證Log檔案所在容器路徑已存在,建議您將該容器路徑掛載到NAS目錄或收集到SLS,以便自動建立目錄以及實現日誌的持久化儲存。 |
| JVM發生OOM時,自動產生Dump檔案。 |
| Dump檔案路徑。需保證Dump檔案所在容器路徑已存在,建議您將該容器路徑掛載到NAS目錄,以便自動建立目錄以及實現日誌的持久化儲存。 |
使用
-XX:+UseContainerSupport參數需JDK 8u191+、JDK 10及以上版本。-XX:+UseContainerSupport參數僅在部分作業系統上支援,具體支援情況請查閱您所使用的Java版本的官方文檔。在JDK 11及之後的版本中,日誌相關的參數
-XX:+PrintGCDetails、-XX:+PrintGCDateStamps、-Xloggc:$LOG_PATH/gc.log已被廢棄,請使用參數-Xlog:gc:$LOG_PATH/gc.log代替。Dragonwell 11不支援
${POD_IP}變數。如果您沒有將/home/admin/nas容器路徑掛載到NAS目錄,則必須保證該目錄在應用啟動前已存在,否則將不會產生記錄檔。
通過-Xms -Xmx限制堆大小
您可以通過設定
-Xms和-Xmx來限制堆大小,但該方式存在以下兩個問題:當執行個體的規格大小調整後,需要重新設定堆大小參數。
當參數設定不合理時,會出現業務應用記憶體未達到JVM堆大小上限,但容器OOM被強制關閉的情況。詳見容器出現137退出碼的含義是什嗎?
推薦的JVM參數設定。
-Xms2048m -Xmx2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof參數說明如下。
參數
說明
-Xms設定JVM初始記憶體大小。建議與
-Xmx相同,避免每次記憶體回收完成後JVM重新分配記憶體。-Xmx設定JVM最大可用記憶體大小。為避免容器OOM,請為系統預留足夠的記憶體大小。
-XX:+PrintGCDetails輸出GC詳細資料。
-XX:+PrintGCDateStamps輸出GC時間戳記。日期形式,例如2019-12-24T21:53:59.234+0800。
-Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').logGC記錄檔路徑。需保證Log檔案所在容器路徑已存在,建議您將該容器路徑掛載到NAS目錄或收集到SLS,以便自動建立目錄以及實現日誌的持久化儲存。
-XX:+HeapDumpOnOutOfMemoryErrorJVM發生OOM時,自動產生Dump檔案。
-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprofDump檔案路徑。需保證Dump檔案所在容器路徑已存在,建議您將該容器路徑掛載到NAS目錄,以便自動建立目錄以及實現日誌的持久化儲存。
推薦的堆大小設定。
記憶體規格大小
JVM堆大小
1 GB
600 MB
2 GB
1434 MB
4 GB
2867 MB
8 GB
5734 MB
在JDK 11及之後的版本中,日誌相關的參數
-XX:+PrintGCDetails、-XX:+PrintGCDateStamps、-Xloggc:$LOG_PATH/gc.log已被廢棄,請使用參數-Xlog:gc:$LOG_PATH/gc.log代替。Dragonwell 11不支援
${POD_IP}變數。如果您沒有將/home/admin/nas容器路徑掛載到NAS目錄,則必須保證該目錄在應用啟動前已存在,否則將不會產生記錄檔。
通過ossutil下載堆轉儲檔案
掛載容器日誌目錄至NAS。具體操作,請參見設定NAS儲存。
設定JVM參數。
其中Dump檔案路徑/home/admin/nas為NAS掛載目錄:
-Xms2048m -Xmx2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof當應用發生OOM時,會產生堆轉儲檔案到NAS掛載目錄,您可以利用ossutil工具,將該Dump檔案下載到本地進行分析。具體操作,請參見通過日誌上傳下載診斷應用。
常見問題
容器出現137退出碼的含義是什嗎?
當容器使用記憶體超過限制時,會出現容器OOM,導致容器被強制關閉。此時業務應用記憶體可能並未達到JVM堆大小上限,所以不會產生Dump日誌。建議您調小JVM堆大小的上限,為容器內其他系統組件預留足夠多的記憶體空間。
為什麼發生OOM卻沒有產生Dump檔案?
當發生OOM Killer時,並不一定會發生JVM OOM,所以不會產生Dump檔案。您可以採取以下方式來避免這種情況。
如果是Java應用,可以適當調小JVM的堆記憶體大小。具體配置,請參見本文。
如果是非Java應用,可以調整執行個體規格,保證充裕的記憶體資源。具體配置,請參見變更執行個體規格。
堆大小和規格記憶體的參數值可以相同嗎?
不可以。因為系統自身組件存在記憶體開銷,例如使用SLS進行日誌收集(設定日誌收集至SLS)時會佔用一小部分的記憶體空間,所以不能將JVM堆大小設定為和規格記憶體大小相同的數值,需要為這些系統組件預留足夠的記憶體空間。
在JDK 8版本下設定-XX:MaxRAMPercentage值為整數時報錯怎麼處理?
這是JDK 8的一個Bug。具體資訊,請參見Java Bug Database。例如,在JDK 8u191版本下,設定-XX:MaxRAMPercentage=70,此時JVM會啟動報錯。
解決方案如下:
方式一:設定
-XX:MaxRAMPercentage為70.0。說明如果您使用了
-XX:InitialRAMPercentage或-XX:MinRAMPercentage,參數值同樣不可設定為整數,需按照方式一的形式來設定。方式二:升級JDK版本至JDK 10及以上版本。
為什麼JVM參數設定了6 GB,但是記憶體使用量率卻很低?
雖然JVM參數已設定-Xms6g -Xmx6g,但是作業系統不會馬上分配6 GB的實體記憶體,需要實際使用後才分配。因此,記憶體使用量率在應用啟動的時候,會相對較低,後續會出現攀爬現象。