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 组件引起的堆内存泄漏(幸运的是我们有更多的工具)。删除应用程序后变得稳定。我不知道这些组件是本身泄漏还是与其他组件组合泄漏,但事实是现在它是稳定的。
输入: 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 组件引起的堆内存泄漏(幸运的是我们有更多的工具)。删除应用程序后变得稳定。我不知道这些组件是本身泄漏还是与其他组件组合泄漏,但事实是现在它是稳定的。