GCP 为 java 应用程序调用 OOMKiller,但它不会消耗最大允许内存

GCP calls OOMKiller for java app however it doesn't consume max allowed memory

输入: GCP、Kubernetes、java 11 spring 引导 2 应用程序

容器启动时内存限制为 1.6GB。 Java 应用程序也在限制内存 -XX:MaxRAMPercentage=80.0。在 "heavy"(不是真的)负载下 - 在大约 4 小时内每 100 毫秒大约有 1 个 http 请求,应用程序被 OOMKiller 杀死。内部诊断工具显示内存远未达到极限:

但是 GCP 工具显示以下内容:

有人怀疑 GCP 正在测量其他东西? POD 仅包含 java 个应用程序(+jaeger 代理)。奇怪的是,在重新启动 GCP 后,如果是内存泄漏,它几乎显示最大内存使用量而不是缓慢增长。

编辑:

Docker 文件:

FROM adoptopenjdk/openjdk11:x86_64-ubuntu-jdk-11.0.3_7-slim
VOLUME /tmp
VOLUME /javamelody
RUN apt-get update && apt-get install procps wget -y
RUN mkdir /opt/cdbg && wget -qO- https://storage.googleapis.com/cloud-debugger/compute-java/debian-wheezy/cdbg_java_agent_gce.tar.gz | tar xvz -C /opt/cdbg
RUN apt-get install fontconfig ttf-dejavu -y
ARG JAR_FILE
ARG VERSION
ARG MODULENAME
ENV TAG=$VERSION
ENV MODULE=$MODULENAME
COPY target/${JAR_FILE} app.jar
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD java -agentpath:/opt/cdbg/cdbg_java_agent.so \
            -Dcom.google.cdbg.module=${MODULE} \
            -Dcom.google.cdbg.version=${TAG} \
            -Djava.security.egd=file:/dev/./urandom \
            -XX:MaxRAMPercentage=80.0 \
            -XX:+CrashOnOutOfMemoryError \
            -XX:ErrorFile=tmp/hs_err_pid%p.log \
            -XX:NativeMemoryTracking=detail \
            -XX:+UnlockDiagnosticVMOptions \
            -XX:+PrintNMTStatistics \
            -XX:+HeapDumpOnOutOfMemoryError \
            -XX:HeapDumpPath=tmp/ \
            -jar /app.jar

和运行它与 Kubernetes(省略了额外的细节):

apiVersion: apps/v1
spec:
  replicas: {{ .Values.replicas }}
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 50%
      maxUnavailable: 0
  template:
    spec:
      initContainers:
        bla-bla
      containers:
        lifecycle:
          preStop:
            exec:
              command: [
                # Gracefully shutdown java
                "pkill", "java"
              ]
        resources:
          limits:
            cpu: 1600
            memory: 1300
          requests:
            cpu: 1600
            memory: 1300

更新 根据 top 命令,内存限制也远未达到限制,但是 CPU 在容器被 OOMKilled 之前,利用率已超过 100%。 Kubernetes 是否有可能杀死试图获得更多 CPU 然后允许的容器?

Tasks:   5 total,   1 running,   4 sleeping,   0 stopped,   0 zombie
%Cpu(s): 34.1 us,  2.0 sy,  0.0 ni, 63.4 id,  0.0 wa,  0.0 hi,  0.5 si,  0.0 st
KiB Mem :  7656868 total,  1038708 free,  2837764 used,  3780396 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  4599760 avail Mem
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
      6 root      20   0 5172744 761664  30928 S 115.3  9.9  21:11.24 java
      1 root      20   0    4632    820    748 S   0.0  0.0   0:00.02 sh
    103 root      20   0    4632    796    720 S   0.0  0.0   0:00.00 sh
    108 root      20   0   38276   3660   3164 R   0.0  0.0   0:00.95 top
    112 root      20   0    4632    788    716 S   0.0  0.0   0:00.00 sh
command terminated with exit code 137

更新2

# pmap -x 7
7:   java -agentpath:/opt/cdbg/cdbg_java_agent.so -Dcom.google.cdbg.module=engine-app -Dcom.google.cdbg.version= -Djava.security.egd=file:/dev/./urandom -XX:MaxRAMPercentage=80.0 -XX:+CrashOnOutOfMemoryError -XX:ErrorFile=tmp/hs_err_pid%p.log -XX:NativeMemoryTracking=detail -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=tmp/ -jar /app.jar
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000       4       4       0 r-x-- java
0000000000400000       0       0       0 r-x-- java
0000000000600000       4       4       4 r---- java
0000000000600000       0       0       0 r---- java
0000000000601000       4       4       4 rw--- java
0000000000601000       0       0       0 rw--- java
00000000006d5000    4900    4708    4708 rw---   [ anon ]
00000000006d5000       0       0       0 rw---   [ anon ]
00000000b0000000   86144   83136   83136 rw---   [ anon ]
00000000b0000000       0       0       0 rw---   [ anon ]
00000000b5420000  350720       0       0 -----   [ anon ]
00000000b5420000       0       0       0 -----   [ anon ]
00000000caaa0000  171944  148928  148928 rw---   [ anon ]
00000000caaa0000       0       0       0 rw---   [ anon ]
00000000d528a000  701912       0       0 -----   [ anon ]
00000000d528a000       0       0       0 -----   [ anon ]
0000000100000000   23552   23356   23356 rw---   [ anon ]
0000000100000000       0       0       0 rw---   [ anon ]
0000000101700000 1025024       0       0 -----   [ anon ]
0000000101700000       0       0       0 -----   [ anon ]
00007f447c000000   39076   10660   10660 rw---   [ anon ]
00007f447c000000       0       0       0 rw---   [ anon ]
00007f447e629000   26460       0       0 -----   [ anon ]
00007f447e629000       0       0       0 -----   [ anon ]
00007f4481c8f000    1280    1164    1164 rw---   [ anon ]
00007f4481c8f000       0       0       0 rw---   [ anon ]
00007f4481dcf000     784       0       0 -----   [ anon ]
00007f4481dcf000       0       0       0 -----   [ anon ]
00007f4481e93000    1012      12      12 rw---   [ anon ]
00007f4481e93000       0       0       0 rw---   [ anon ]
00007f4481f90000      16       0       0 -----   [ anon ]
...
00007ffcfcd48000       8       4       0 r-x--   [ anon ]
00007ffcfcd48000       0       0       0 r-x--   [ anon ]
ffffffffff600000       4       0       0 r-x--   [ anon ]
ffffffffff600000       0       0       0 r-x--   [ anon ]
---------------- ------- ------- -------
total kB         5220936  772448  739852

此 pmap 在 OOMKilled 前不久被调用。 5GB?为什么top不显示这个?也不确定如何解释 pmap 命令结果

根据日志文件,有超过 10,000 个已启动的线程。这是 很多,即使我们不考虑为容器保留的 2 CPUs/cores(limits.cpu = request.cpu = 1600 毫核) .

每个线程及其堆栈都分配在与堆分开的内存中。 OOM问题很有可能是启动线程数量多造成的。

JVM 以本机内存跟踪相关选项启动(-XX:NativeMemoryTracking=detail, -XX:+UnlockDiagnosticVMOptions, -XX:+PrintNMTStatistics) 可以帮助查看内存使用情况,包括那些线程消耗的内存。This doc 可以作为 Java 11.

无论如何,强烈建议不要启动那么多线程。例如。使用游泳池,在不再需要时启动和停止它们...

容器 OOM 终止的原因有两个:容器配额和系统配额。

OOM Killer 触发与内存相关的问题。

如果您的系统远未内存不足,则可能是您的容器存在限制。 对于你在pod里面的进程来说,pod资源限制就像整个系统OOM一样。

  • 检查您的 pod 清单,可能在 pods.
  • 中达到了限制设置

此外,值得检查资源请求,因为默认情况下它们未设置。 请求必须小于或等于容器限制。这意味着如果多个容器同时使用比各自请求更多的内存,则容器可能会在节点上过度使用并被 OOMK 杀死。

  • 检查分配给每个 pod 的内存量以及进程在多长时间内实际使用了多少内存。也许它保留了比实际需要更多的内存,这导致您的内存使用率从更高的水平开始。

在我的例子中,问题出在位于 Docker 文件

的 CMD 行中的调试器组件
-agentpath:/opt/cdbg/cdbg_java_agent.so \
            -Dcom.google.cdbg.module=${MODULE} \
            -Dcom.google.cdbg.version=${TAG} \
            -Djava.security.egd=file:/dev/./urandom \

删除应用程序后停止泄漏。但消失的只是本机内存泄漏。正如后来调查的那样,还有由 jaegger tracer 组件引起的堆内存泄漏(幸运的是我们有更多的工具)。删除应用程序后变得稳定。我不知道这些组件是本身泄漏还是与其他组件组合泄漏,但事实是现在它是稳定的。