为什么我设置的巨细关系没有错,还会OOMKilled?

这种问题常发生在JDK8u131或许JDK9版别之后所呈现在容器中运转JVM的问题:在大多数状况下,JVM将一般默许会选用宿主机Node节点的内存为Native VM空间(其间包含了堆空间、直接内存空间以及栈空间),而并非是是容器的空间为规范。

堆内存和VM实际分配内存不一致

-XshowSettings:vm

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

Jps -lVvm

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

咱们在运转的时分将JVM堆内存内存设置为3000MB,而-XshowSettings:vm打印出的JVM将最大堆巨细为1.09G,假如按照这个内存进行分配内存的话很可能会导致实际内存和预分配内存所形成的不一致问题。

怎么解决此问题

JVM 感知 cgroup 约束

解决JVM内存超限的问题,这种办法能够让JVM主动感知Docker容器的cgroup约束,从而动态的调整堆内存巨细。

JDK8u131在JDK9中有一个很好的特性,即JVM能够检测在Docker容器中运转时有多少内存可用。为了使jvm保留依据容器规范的内存,有必要设置标志-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

留意:假如将这两个标志与Xms和Xmx标志一同设置,那么jvm的行为将是什么?-Xmx标志将掩盖-XX:+ UseCGroupMemoryLimitForHeap标志

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

参数剖析

  • -XX:+ UseCGroupMemoryLimitForHeap标志使JVM能够检测容器中的最大堆巨细。
  • -Xmx标志将最大堆巨细设置为固定巨细。

除了JVM的堆空间,还会关于非堆Noheap和JVM的东西,还会有一些额定的内存运用状况。

运用JDK9的容器感知机制尝试

设置了容器有4GB内存分配,而JVM运用1GM作为最大堆,由于容器中除了JVM之外没有其他进程在运转,所以咱们还能够进一步扩展一下关于Heap堆的分配?

-XX:MaxRAMFraction

在较低的版别的时分能够运用-XX:MaxRAMFraction参数,它告诉JVM运用可用内存/MaxRAMFract作为最大堆。运用-XX:MaxRAMFraction=1,咱们将简直一切可用内存用作最大堆。

问题剖析

最大堆占用总内存是否依然会导致你的进程由于内存的其他部分(如“元空间”)而被杀死?

答案:MaxRAMFraction=1仍将为其他非堆内存留出一些空间

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

留意:假如容器运用堆外内存,这可能会有危险,由于简直一切的容器内存都分配给了堆。您有必要将-XX:MaxRAMFraction=2设置为堆只运用50%的容器内存,或许运用Xmx。

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

容器内部感知CGroup资源约束

Docker1.7开端将容器cgroup信息挂载到容器中,所以运用能够从 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件获取内存、 CPU等设置,在容器的运用发动指令中依据Cgroup装备正确的资源设置 -Xmx, -XX:ParallelGCThreads 等参数

Java10中,改进了容器集成

Java10+废除了-XX:MaxRAM参数,由于JVM将正确检测该值。在Java10中,改进了容器集成,无需增加额定的标志,JVM将运用1/4的容器内存用于堆。

java10+确实正确地识别了内存的docker约束,但您能够运用新的标志MaxRAMPercentage(例如:-XX:MaxRAMPercentage=75)而不是旧的MaxRAMFraction,以便更精确地调整堆的巨细。

java10+上的UseContainerSupport选项,而且是默许启用的,不用设置。同时 UseCGroupMemoryLimitForHeap 这个就弃用了,不建议持续运用,同时还能够经过 -XX:InitialRAMPercentage、-XX:MaxRAMPercentage、-XX:MinRAMPercentage 这些参数愈加细腻的操控 JVM 运用的内存比率。

-XX:MaxRAMFraction

Java 程序在运转时会调用外部进程、请求 Native Memory 等,所以即使是在容器中运转 Java 程序,也得预留一些内存给系统的。所以 -XX:MaxRAMPercentage 不能装备得太大。当然依然能够运用-XX:MaxRAMFraction=1选项来紧缩容器中的一切内存。

上面咱们知道了怎么进行设置和操控对应的堆内存和容器内存的之间的关系,所以避免JVM的堆内存超过了容器内存,导致容器呈现OOMKilled的状况。可是在整个JVM进程系统而言,不只仅只包含了Heap堆内存,其实还有其他相关的内存存储空间是需要咱们考虑的,一边避免这些内存空间会形成咱们的容器内存溢出的场景。

Off Heap Space

接下来了咱们需要进行剖析出heap之外的一部分便是对外内存便是Off Heap Space,也便是Direct buffer memory堆外内存。首要经过的方法便是选用Unsafe方法进行请求内存,大多数场景也会经过Direct ByteBuffer方法进行获取。好废话不多说进入正题。

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

JVM参数MaxDirectMemorySize

研究一下jvm的-XX:MaxDirectMemorySize,该参数指定了DirectByteBuffer能分配的空间的限额,假如没有显示指定这个参数发动jvm,默许值是xmx对应的值(低版别是减去幸存区的巨细)。

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

而Runtime.maxMemory()在HotSpot VM里的实现是:

-Xmx减去一个survivor space的预留巨细

DirectByteBuffer目标是一种典型的”冰山目标”,在堆中存在少数的走漏的目标,但其下面衔接用堆外内存,这种状况容易形成内存的大量运用而得不到开释

【JVM故障问题排查心得】「内存诊断系列」Xmx和Xms的大小是小于Docker容器以及Pod的大小的,为啥还是会出现OOMKilled?

-XX:MaxDirectMemorySize=size 用于设置 New I/O (java.nio) direct-buffer allocations 的最大巨细,size 的单位能够运用 k/K、m/M、g/G;假如没有设置该参数则默许值为 0,意味着JVM自己主动给NIO direct-buffer allocations选择最大巨细。

-XX:MaxDirectMemorySize的默许值是什么?

  • 在sun.misc.VM中,它是Runtime.getRuntime.maxMemory(),这便是运用-Xmx装备的内容。而对应的JVM参数怎么传递给JVM底层的呢?首要经过hotspot/share/prims/jvm.cpp。

  • jvm.cpp里头有一段代码用于把 -XX:MaxDirectMemorySize 指令参数转换为key为 sun.nio.MaxDirectMemorySize的特点。咱们能够看出来他转换为了该特点之后,进行设置和初始化直接内存的装备。针关于直接内存的核心类就在, 在-XX:MaxDirectMemorySize 是用来装备NIO direct memory上限用的VM参数。但假如不装备它的话,direct memory默许最多能请求多少内存呢?这个参数默许值是-1,明显不是一个“有效值”。

sun.nio.MaxDirectMemorySize 特点,假如为 null 或许是空或许是 – 1,那么则设置为 Runtime.getRuntime ().maxMemory ();由于当MaxDirectMemorySize参数没被显式设置时它的值便是-1,在Java类库初始化时maxDirectMemory()被java.lang.System的静态构造器调用。

这个max_capacity()实际回来的是 -Xmx减去一个survivor space的预留巨细

结论剖析阐明

MaxDirectMemorySize没显式装备的时分,NIO direct memory可请求的空间的上限便是-Xmx减去一个survivor space的预留巨细。例如假如您不装备-XX:MaxDirectMemorySize并装备-Xmx5g,则”默许” MaxDirectMemorySize也将是5GB-survivor space区,而且运用程序的总堆+直接内存运用量可能会增长到5 + 5 = 10 Gb 。

其他获取 maxDirectMemory 的值的API办法

BufferPoolMXBean 及 JavaNioAccess.BufferPool (经过SharedSecrets获取) 的 getMemoryUsed 能够获取 direct memory 的巨细;其间 java9 模块化之后,SharedSecrets 从原来的 sun.misc.SharedSecrets 变更到 java.base 模块下的 jdk.internal.access.SharedSecrets;要运用 –add-exports java.base/jdk.internal.access=ALL-UNNAMED 将其导出到 UNNAMED,这样才能够运转

内存剖析问题

-XX:+DisableExplicitGC 与 NIO的direct memory

用了-XX:+DisableExplicitGC参数后,System.gc()的调用就会变成一个空调用,彻底不会触发任何GC(可是“函数调用”本身的开支还是存在的哦~)。

做ygc的时分会将新生代里的不可达的DirectByteBuffer目标及其堆外内存收回了,可是无法对old里的DirectByteBuffer目标及其堆外内存进行收回,这也是咱们通常碰到的最大的问题,假如有大量的DirectByteBuffer目标移到了old,可是又一直没有做cms gc或许full gc,而只进行ygc,那么咱们的物理内存可能被慢慢耗光,可是咱们还不知道发生了什么,由于heap明明剩下的内存还许多(前提是咱们禁用了System.gc)。