容器内的 Java 应用可能会发生两种类型的 OOM 异常,
JVM 的 OOM
:
JVM 的堆栈元空间等内存泄漏
,导致没有足够的内存来为对象分配空间并且GC也没有空间可回收时,这时JVM会主动抛出错误并退出进程,并留下相应的错误记录。容器退出状态为exit code 137 reason: error(
137表示容器收到SIGKILL信号而失败,通常是达到资源限制或探针失败
)
容器 OOM
:
一般是JVM参数设置不合理
,导致container_memory_working_set_bytes达到了cgroups限制,会在k8s事件中记录且容器退出状态为exit code 137 reason: OOM Killed
从容器来看
container_memory_working_set_bytes代表容器真实使用的内存量,也是判断超过limit的限制,超过limit则会导致oom;
container_memory_working_set_bytes = container_memory_usage_bytes – total_inactive_file(不活跃缓冲页);
container_memory_usage_bytes = container_memory_rss(进程实际使用的物理内存)+container_memory_cache(页面缓存)+kernel memory;
container_memory_cache = total_active_file + inactive_file;
从jvm来看
进程实际使用的物理内存container_memory_rss = JVM内存(堆栈元空间)+直接内存+其他(文件描述符、GC消耗等等);
容器真实使用的内存量container_memory_working_set_bytes = JVM内存(堆栈元空间)+直接内存+其他(文件描述符、GC消耗等等)+container_memory_cache(页面缓存)+kernel memory;
而预留内存又=直接内存+其他(文件描述符、GC消耗等等)+container_memory_cache(页面缓存)+kernel memory;
综上,容器真实使用的内存为
JVM内存(堆栈元空间)+ 预留内存(直接内存+文件描述符、GC消耗等 + 活跃的缓存页 + kernel memory)
一种解决 JVM 内存超限的方法
可以让 JVM 自动感知 docker 容器的 cgroup 限制,从而动态的调整堆内存大小。JDK8u131 在 JDK9 中有一个很好的特性,即 JVM 能够检测在 Docker 容器中运行时有多少内存可用。为了使 jvm 保留根据容器规范的内存,必须设置标志 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap。
注意:如果将这两个标志与 Xms 和 Xmx 标志一起设置,那么 jvm 的行为将是什么?
-Xmx 标志将覆盖-XX:+ UseCGroupMemoryLimitForHeap 标志。
MaxDirectMemorySize
-XX:MaxDirectMemorySize=size用于设置New I/O(
java.nio
) direct-buffer allocations的峰值,-XX:MaxDirectMemorySize 没显式配置的时候,NIO direct memory 可申请的空间的上限就是 -Xmx 减去一个 survivor space 的预留大小。如果不配置
-XX:MaxDirectMemorySize
并配置 -Xmx5G,则 默认 MaxDirectMemorySize 也将是 5G-survivor space 区,并且应用程序的总堆+直接内存使用量可能会增长到
5G + 5G = 10 G
。