避免 OOMKilled:在 Kubernetes 環境中最佳化 Java 程序的記憶體配置

技术颜良發表於2024-06-10

避免 OOMKilled:在 Kubernetes 環境中最佳化 Java 程序的記憶體配置

DevOps雲學堂譯 奇妙的Linux世界 2024-06-10 09:53 重慶 聽全文
公眾號關注 「奇妙的 Linux 世界」
設為「星標」,每天帶你玩轉 Linux !
圖片

管理 Kubernetes Pod 中執行的 Java 程序的記憶體使用情況比人們想象的更具挑戰性。即使使用正確的 JVM 記憶體配置,仍然可能會出現OOMKilled問題,您想知道為什麼嗎?

奇妙的Linux世界
Linux 愛好者聚集地,各種硬核乾貨文章和新奇內容推薦,定期發放福利紅包。快加入我們,一起愉快玩耍!
229篇原創內容

長話短說

由於 JVM 僅考慮大小限制,因此無法保證 Java 程序的完整heap記憶體邊界(堆記憶體);不是non-heap 記憶體(非堆記憶體),這取決於多種因素。從堆記憶體非堆記憶體的比例為 75% 開始,並密切關注記憶體的行為。如果事情失控,您可以調整 pod 的記憶體限制調整heap-to-non-heap比率來避免 OOMKilled 事故。

Context語境

我們在 Kubernetes 中執行的生產 Java 應用程式反覆遇到 OOMKilled重啟問題。儘管在 pod 和 JVM 級別都定義了記憶體設定,但 pod 的總記憶體使用量波動導致頻繁重啟

  • Pod 級別配置:我們最初將 Pod 的記憶體限制設定為 2Gi,使用以下設定:
resources:
requests:
memory: "2Gi"
cpu: "4"
limits:
memory: "2Gi"
cpu: "4"
  • JVM 級別配置:我們指定了 JVM 應使用的系統記憶體百分比,以允許 JVM 適應其環境。
-XX:MaxRAMPercentage=80.0

需要注意的是,這MaxRAMPercentage並不限制 Java 程序可以使用的總記憶體大小。它特指 JVMheap大小,因為堆是應用程式可訪問和使用的唯一記憶體。透過這些設定,Pod 擁有2Gi系統記憶體,其中的系統記憶體1.6Gi被分配給堆並且0.4Gi可供非堆記憶體使用。(請記住,2Gi等於2 * 1024 * 1024 * 1024 = 2.15GB,因為監控指標用作GB儀表板上的記憶體單位。)

解決該問題的初步嘗試

為了緩解OOMKilled問題,我們將 pod 的記憶體限制從 2Gi增加4Gi, 這確實有助於減少問題。然而,仍然存在一些問題:

  1. 為什麼container_memory_working_setcontainer_memory_rss接近 100%,而 JVM 堆和非堆使用率卻顯著降低?

圖片
2. 鑑於 Java 程序是 pod 中執行的唯一程序,為什麼工作集大小 (WSS)/駐留集大小 (RSS) 記憶體使用量超過 JVM 總記憶體?
圖片
3. 為什麼程序記憶體使用率仍然接近100%,幾乎達到Pod記憶體限制?
圖片

分析

為什麼Java總記憶體使用量遠低於系統記憶體使用量?

圖片
我們注意到,一旦提交的堆記憶體達到最大堆大小container_memory_working_setcontainer_memory_rss 就會停止增加。
圖片
➊提交的 JVM Heap 一旦達到heap限制就停止增加❷ ❸當提交的記憶體達到限制時,WSS/RSS 的系統記憶體停止heap增加。根據MemoryUsage類的 Java 文件,這些指標來自:

public long getCommited()
返回提交供Java 虛擬機器使用的記憶體量(以位元組為單位)。這個記憶體量是保證Java虛擬機器使用的。

提交的記憶體表示 JVM 從作業系統預先分配的記憶體。因此,從容器/Pod 的角度來看,WSS/RSS 使用率顯得很高,而在 JVM 內,堆記憶體和非堆記憶體使用率仍然很低。
這也解釋了為什麼在 pod 被OOMKilled之前沒有發生 OutOfMemory 異常,因為堆記憶體和非堆記憶體都沒有達到 JVM 的限制。相反,JVM 會從作業系統中預先分配和保留記憶體,而不會輕易釋放它。OpenJDK規範解釋道:

G1 僅在 Full GC 或併發週期期間從 Java 堆返回記憶體。由於 G1 盡力完全避免 Full GC,並且僅根據 Java 堆佔用和分配活動觸發併發週期,因此它不會返回 Java 堆在許多情況下,除非從外部強制這樣做,否則都會有記憶體。這種行為在資源按使用付費的容器環境中尤其不利。即使在 VM 由於不活動而僅使用其分配的記憶體資源的一小部分的階段,G1 也將保留所有 Java 堆。--https://openjdk.org/jeps/346

因此,雖然Java程序的實際記憶體使用量可能很低,但JVM預分配的提交記憶體可能會高得多,並且不會立即返回給系統。

圖片

為什麼 WSS/RSS 記憶體使用量超過 JVM 總記憶體?

在檢查了系統記憶體的來源和 JVM 指標後,這對我來說仍然是一個謎。

圖片
系統記憶體 RSS 與 JVM 總提交記憶體之間的差距
➊系統記憶體 WSS 為 3.8GB
❷ JVMheap提交的記憶體為 3.22GB
❸ JVM 總提交的記憶體為 3.42GB
Pod 中執行的 JVM 的本機記憶體跟蹤 (NMT) 報告為我們提供了 Java 程序中記憶體使用情況的詳細細分,尤其是記憶體non-heap。結果與JVM Heap和JVM Total指標一致。

Native Memory Tracking:

Total: reserved=5066125KB, committed=3585293KB
- Java Heap (reserved=3145728KB, committed=3145728KB)
(mmap: reserved=3145728KB, committed=3145728KB)
- Class (reserved=1150387KB, committed=113419KB)
- Thread (reserved=297402KB, committed=32854KB)
- Code (reserved=253098KB, committed=73782KB)
- GC (reserved=174867KB, committed=174867KB)
- Compiler (reserved=2156KB, committed=2156KB)
- Internal (reserved=11591KB, committed=11591KB)
- Other (reserved=2690KB, committed=2690KB)
- Symbol (reserved=21454KB, committed=21454KB)
- Native Memory Tracking (reserved=6275KB, committed=6275KB)
- Arena Chunk (reserved=195KB, committed=195KB)
- Logging (reserved=4KB, committed=4KB)
- Arguments (reserved=29KB, committed=29KB)
- Module (reserved=249KB, committed=249KB)

系統記憶體使用 WSS/RSS已透過 Pod 中執行命令的RES記憶體(程序使用的常駐記憶體量)來確認。topJava 程序是 pod 中唯一執行的程序。

USER   PID    %CPU %MEM  VSZ      RSS      TTY  STAT START TIME   COMMAND
xxx-+ 1 7.7 0.4 24751760 3818536 ? Ssl Jul28 340:41 /usr/java/jdk-11.0.17/bin/java -XX:MaxRAMPercentage=75.0 -XshowSettings:vm -classpath ...
xxx-+ 80559 0.0 0.0 50548 3936 ? Rs 07:02 0:00 ps -aux

因此,這兩個指標都是值得信賴的,但它們之間仍然存在 300MB 左右的差距。

為什麼增加 Pod 記憶體限制後系統記憶體使用率仍然接近 100%?

首先,它是resources.limits.memory確定系統記憶體大小而不是resources.requests.memory. 後者只是讓 Kubernetes 叢集找到與請求的記憶體匹配的節點來在其上執行 pod。
其次,如前所述,heapJVM 只能指定並嚴格控制記憶體的大小,而不能指定non/off-heap記憶體。因此,即使系統記憶體增加,non/off-heap記憶體使用量也可能成比例增加。
為了緩解這種情況,減少記憶體百分比heap可以提供更多空間non/off-heap。所以這是我們嘗試的下一個選項:MaxRAMPercentage從減少80%到75%並按預期工作:WSS/RSS 下降。

圖片
減少堆百分比之前:➊❷ WSS/RSS 仍接近 Pod 記憶體限制 (4.29GB)

圖片
減少堆百分比後 ➊❷ WSS/RSS 穩定在 3.6GB,並且與 pod 記憶體限制 (4.29GB) 有安全餘量

結論

可以使用以下方法來解決 Java 程序記憶體使用的不確定性並消除 pod OOMKilled問題:

  1. 從一個合理的值開始MaxRAMPercentage,這75%通常是一個很好的起點。
  2. 隨著時間的推移監控heap使用情況和系統記憶體WSS/RSS。
  • 如果您的最大heap使用率很高(即保持在>90% 範圍內),則這是增加 pod 記憶體限制的訊號 ( resources.limits.memory)。您heap需要更多空間。
  • 如果最大heap使用率正常(即保持遠低於<90%),但WSS/RSS較高且接近程序限制,請考慮減少MaxRAMPercentage為空間分配更多記憶體non/off-heap。
  • 監控最大值WSS/RSS以確保 Pod 記憶體限制始終有 5% 到 10% 的安全裕度。不要飛得太靠近太陽!

文章翻譯 https://medium.com/@karthik.jeyapal/memory-settings-for-java-process-running-in-kubernetes-pod-6d0a2e092ce5

本文轉載自:「 雲原生百寶箱」,原文:https://url.hi-linux.com/tXoNb,版權歸原作者所有。歡迎投稿,投稿郵箱: editor@hi-linux.com。

圖片

最近,我們建立了一個技術交流微信群。目前群裡已加入了不少行業內的大神,有興趣的同學可以加入和我們一起交流技術,在 「奇妙的 Linux 世界」 公眾號直接回復 「加群」 邀請你入群。

圖片

你可能還喜歡

點選下方圖片即可閱讀

圖片

GitHub 星標 10.3K:一個更適合新手的 Curl 替代工具

圖片
點選上方圖片,『美團|餓了麼』外賣紅包天天免費領

圖片

更多有趣的網際網路新鮮事,關注「奇妙的網際網路」影片號全瞭解!

雲端計算 · 目錄
上一篇Kubernetes 十週年
閱讀 128

相關文章