避免 OOMKilled:在 Kubernetes 环境中优化 Java 进程的内存配置
DevOps云学堂译 奇妙的Linux世界 2024-06-10 09:53 重庆 听全文 公众号关注 「奇妙的 Linux 世界」设为「星标」,每天带你玩转 Linux !
管理 Kubernetes Pod 中运行的 Java 进程的内存使用情况比人们想象的更具挑战性。即使使用正确的 JVM 内存配置,仍然可能会出现OOMKilled
问题,您想知道为什么吗?
长话短说
由于 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,
这确实有助于减少问题。然而,仍然存在一些问题:
- 为什么
container_memory_working_set
和container_memory_rss
接近100%
,而 JVM 堆和非堆使用率却显着降低?
2. 鉴于 Java 进程是 pod 中运行的唯一进程,为什么工作集大小 (WSS)/驻留集大小 (RSS) 内存使用量
超过 JVM 总内存?
3. 为什么进程内存使用率仍然接近100%,几乎达到Pod内存限制?
分析
为什么Java总内存使用量远低于系统内存使用量?
我们注意到,一旦提交的堆内存达到最大堆大小
,container_memory_working_set
和container_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问题:
- 从一个合理的值开始
MaxRAMPercentage
,这75%
通常是一个很好的起点。 - 随着时间的推移监控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 替代工具
点击上方图片,『美团|饿了么』外卖红包天天免费领
更多有趣的互联网新鲜事,关注「奇妙的互联网」视频号全了解!
云计算159 软件12 开源539 云原生273 程序员451 云计算 · 目录 上一篇Kubernetes 十周年 阅读 128 喜欢此内容的人还喜欢 Github 星标 46K,这份开源系统设计指南太赞了! 我常看的号 奇妙的Linux世界 不看的原因- 内容低质
- 不看此公众号内容
- 内容低质
- 不看此公众号内容
- 内容低质
- 不看此公众号内容
人划线
标签:reserved,heap,Kubernetes,OOMKilled,内存,JVM,committed,Java From: https://www.cnblogs.com/cheyunhua/p/18240450