前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >K8s: Java应用OOMKilled的原因与分析

K8s: Java应用OOMKilled的原因与分析

作者头像
DevOps云学堂
发布2023-12-13 13:24:59
6810
发布2023-12-13 13:24:59
举报
文章被收录于专栏:DevOps持续集成DevOps持续集成

本篇文章是「DevOps云学堂」与你共同进步的第 65

管理 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,使用以下设置:
代码语言:javascript
复制
resources:
  requests:
    memory: "2Gi"
    cpu: "4"
  limits:
    memory: "2Gi"
    cpu: "4"
  • JVM 级别配置:我们指定了 JVM 应使用的系统内存百分比,以允许 JVM 适应其环境。
代码语言:javascript
复制
-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指标一致。

代码语言:javascript
复制
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 中唯一运行的进程。

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DevOps云学堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 长话短说
  • Context语境
  • 解决该问题的初步尝试
  • 分析
    • 为什么Java总内存使用量远低于系统内存使用量?
      • 为什么 WSS/RSS 内存使用量超过 JVM 总内存?
        • 为什么增加 Pod 内存限制后系统内存使用率仍然接近 100%?
        • 结论
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档