美文网首页
JAVA: 容器中的JVM资源该如何被安全的限制?

JAVA: 容器中的JVM资源该如何被安全的限制?

作者: EricJonse | 来源:发表于2019-05-14 18:39 被阅读0次

    本文转自:https://qingmu.io

    前言

    Java与Docker的结合,虽然更好的解决了application的封装问题。但也存在着不兼容,比如Java并不能自动的发现Docker设置的内存限制,CPU限制。

    这将导致JVM不能稳定服务业务!容器会杀死你JVM进程,而健康检查又将拉起你的JVM进程,进而导致你监控你的pod一天重启次数甚至能达到几百次。

    我们希望当Java进程运行在容器中时,java能够自动识别到容器限制,获取到正确的内存和CPU信息,而不用每次都需要在kubernetes的yaml描述文件中显示的配置完容器,还需要配置JVM参数。

    使用JVM MaxRAM参数或者解锁实验特性的JVM参数,升级JDK到10+,我们可以解决这个问题(也许吧.)。

    首先Docker容器本质是是宿主机上的一个进程,它与宿主机共享一个/proc目录,也就是说我们在容器内看到的/proc/meminfo,/proc/cpuinfo 与直接在宿主机上看到的一致,如下。

    Host

    cat /proc/meminfo 
    MemTotal:       197869260 kB
    MemFree:         3698100 kB
    MemAvailable:   62230260 kB
    

    容器

    docker run -it --rm alpine cat /proc/meminfo
    MemTotal:       197869260 kB
    MemFree:         3677800 kB
    MemAvailable:   62210088 kB
    

    那么Java是如何获取到Host的内存信息的呢?没错就是通过/proc/meminfo来获取到的。

    默认情况下,JVM的Max Heap Size是系统内存的1/4,假如我们系统是8G,那么JVM将的默认Heap≈2G。

    Docker通过CGroups完成的是对内存的限制,而/proc目录是已只读形式挂载到容器中的,由于默认情况下Java 压根就看不见CGroups的限制的内存大小,而默认使用/proc/meminfo中的信息作为内存信息进行启动, 这种不兼容情况会导致,如果容器分配的内存小于JVM的内存,JVM进程会被理解杀死。

    内存限制不兼容

    我们首先来看一组测试,这里我们采用一台内存为188G的物理机。

    #free -g
                  total        used        free      shared  buff/cache   available
    Mem:            188         122           1           0          64          64
    

    以下的测试中,我们将包含openjdk的hotspot虚拟机,IBM的openj9虚拟机。

    以下测试中,我们把正确识别到限制的jdk,称之为安全(即不会超出容器限制不会被kill),反之称之为危险。

    测试用例1(OPENJDK)

    这一组测试我们使用最新的openjdk8-12,给容器限制内存为4G,看JDK默认参数下的最大堆为多少?看看我们默认参数下多少版本的JDK是安全的

    命令如下,如果你也想试试看,可以用一下命令。

    docker run -m 4GB  --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version
    docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version
    docker run -m 4GB --rm  openjdk:10-jre-slim java -XshowSettings:vm  -version
    docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  -version
    docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version
    

    OpenJDK8(并没有识别容器限制,26.67G) 危险

    [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version
    
    VM settings:
        Max. Heap Size (Estimated): 26.67G
        Ergonomics Machine Class: server
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk version "1.8.0_181"
    OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)
    OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)
    

    OpenJDK8 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap (正确的识别容器限制,910.50M)安全

    [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings
    VM settings:
        Max. Heap Size (Estimated): 910.50M
        Ergonomics Machine Class: server
        Using VM: OpenJDK 64-Bit Server VM
    openjdk version "1.8.0_181"
    OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)
    OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)
    

    OpenJDK 9(并没有识别容器限制,26.67G)危险

    [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version
    VM settings:    Max. Heap Size (Estimated): 29.97G
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk version "9.0.4"
    OpenJDK Runtime Environment (build 9.0.4+12-Debian-4)
    OpenJDK 64-Bit Server VM (build 9.0.4+12-Debian-4, mixed mode)
    

    OpenJDK 9 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap (正确的识别容器限制,1G)安全

    [root@node94 ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm  -version
    VM settings:
        Max. Heap Size (Estimated): 440.81M
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk version "9.0.4"
    OpenJDK Runtime Environment (build 9.0.4+12-Debian-4)
    OpenJDK 64-Bit Server VM (build 9.0.4+12-Debian-4, mixed mode)
    

    OpenJDK 10(正确的识别容器限制,1G)危险 我测试的是危险,但是其他文档是安全??? 不知道为什么。

    [root@node94 ~]# docker run -m 4GB --rm  openjdk:10-jre-slim java -XshowSettings:vm -XX:MaxRAMFraction=1 --version
    VM settings:
        Max. Heap Size (Estimated): 3.87G
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk 10.0.2 2018-07-17
    OpenJDK Runtime Environment (build 10.0.2+13-Debian-2)
    OpenJDK 64-Bit Server VM (build 10.0.2+13-Debian-2, mixed mode)
    

    OpenJDK 10 危险的原因 添加了 -XX:MaxRAMFraction=1 反而危险 不添加 -XX:MaxRAMFraction=1 如下安全

    [root@node94 ~]# docker run -m 4GB --rm  openjdk:10-jre-slim java -XshowSettings:vm  --version
    VM settings:
        Max. Heap Size (Estimated): 989.88M
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk 10.0.2 2018-07-17
    OpenJDK Runtime Environment (build 10.0.2+13-Debian-2)
    OpenJDK 64-Bit Server VM (build 10.0.2+13-Debian-2, mixed mode)
    

    OpenJDK 10 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap(正确的识别容器限制,1G)安全

    [root@node94 ~]# docker run -m 4GB --rm  openjdk:10-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm   --version
    OpenJDK 64-Bit Server VM warning: Option UseCGroupMemoryLimitForHeap was deprecated in version 10.0 and will likely be removed in a future release.
    VM settings:
        Max. Heap Size (Estimated): 989.88M
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk 10.0.2 2018-07-17
    OpenJDK Runtime Environment (build 10.0.2+13-Debian-2)
    OpenJDK 64-Bit Server VM (build 10.0.2+13-Debian-2, mixed mode)
    

    OpenJDK 11(正确的识别容器限制,1G)安全

    [root@node94 ~]# docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  --version
    VM settings:
        Max. Heap Size (Estimated): 989.88M
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk 11.0.3 2019-04-16
    OpenJDK Runtime Environment (build 11.0.3+1-Debian-1bpo91)
    OpenJDK 64-Bit Server VM (build 11.0.3+1-Debian-1bpo91, mixed mode, sharing)
    

    OpenJDK 12(正确的识别容器限制,1G)安全

    [root@node94 ~]# docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  --version
    VM settings:
        Max. Heap Size (Estimated): 989.88M
        Using VM: OpenJDK 64-Bit Server VM
    
    openjdk 12.0.1 2019-04-16
    OpenJDK Runtime Environment (build 12.0.1+12)
    OpenJDK 64-Bit Server VM (build 12.0.1+12, mixed mode, sharing)
    

    测试用例2(IBMOPENJ9)

    docker run -m 4GB --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java -XshowSettings:vm  -version
    docker run -m 4GB --rm  adoptopenjdk/openjdk9-openj9:alpine-slim  java -XshowSettings:vm  -version
    docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java -XshowSettings:vm  -version
    docker run -m 4GB --rm  adoptopenjdk/openjdk11-openj9:alpine-slim  java -XshowSettings:vm  -version
    

    openjdk8-openj9 (正确的识别容器限制,3G)安全 这里出现了错误,限制内存512MB 而且是client模式如何改为server模式目前没找到,但是其他文档上成立的 server模式内存限制3G。

    [root@node94 ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java  -XshowSettings:vm   -version
    VM settings:
        Max. Heap Size (Estimated): 512.00M
        Ergonomics Machine Class: client
        Using VM: Eclipse OpenJ9 VM
    
    openjdk version "1.8.0_212"
    OpenJDK Runtime Environment (build 1.8.0_212-b03)
    Eclipse OpenJ9 VM (build openj9-0.14.0, JRE 1.8.0 Linux amd64-64-Bit Compressed References 20190417_286 (JIT enabled, AOT enabled)
    OpenJ9   - bad1d4d06
    OMR      - 4a4278e6
    JCL      - 5590c4f818 based on jdk8u212-b03)
    

    openjdk9-openj9 (正确的识别容器限制,3G)安全 同上面一样

    [root@node94 ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk9-openj9:alpine-slim  java  -XshowSettings:vm   -version
    VM settings:
        Max. Heap Size (Estimated): 512.00M
        Using VM: Eclipse OpenJ9 VM
    
    openjdk version "9.0.4-adoptopenjdk"
    OpenJDK Runtime Environment (build 9.0.4-adoptopenjdk+12)
    Eclipse OpenJ9 VM (build openj9-0.9.0, JRE 9 Linux amd64-64-Bit Compressed References 20180814_248 (JIT enabled, AOT enabled)
    OpenJ9   - 24e53631
    OMR      - fad6bf6e
    JCL      - feec4d2ae based on jdk-9.0.4+12)
    

    openjdk10-openj9 (正确的识别容器限制,3G)安全 同上

    [root@node94 ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java  -XshowSettings:vm   -version
    VM settings:
        Max. Heap Size (Estimated): 512.00M
        Using VM: Eclipse OpenJ9 VM
    
    openjdk version "10.0.2-adoptopenjdk" 2018-07-17
    OpenJDK Runtime Environment (build 10.0.2-adoptopenjdk+13)
    Eclipse OpenJ9 VM (build openj9-0.9.0, JRE 10 Linux amd64-64-Bit Compressed References 20180813_102 (JIT enabled, AOT enabled)
    OpenJ9   - 24e53631
    OMR      - fad6bf6e
    JCL      - 7db90eda56 based on jdk-10.0.2+13)
    

    openjdk11-openj9(正确的识别容器限制,3G)安全

    [root@node94 ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk11-openj9:alpine-slim  java  -XshowSettings:vm   -version
    VM settings:
        Max. Heap Size (Estimated): 512.00M
        Using VM: Eclipse OpenJ9 VM
    
    openjdk version "11.0.3" 2019-04-16
    OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.3+7)
    Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.14.0, JRE 11 Linux amd64-64-Bit Compressed References 20190417_202 (JIT enabled, AOT enabled)
    OpenJ9   - bad1d4d06
    OMR      - 4a4278e6
    JCL      - 5cc996a803 based on jdk-11.0.3+7)
    

    分析

    分析之前我们先了解这么一个情况:

    JavaMemory (MaxRAM) = 元数据+线程+代码缓存+OffHeap+Heap...
    

    一般我们都只配置Heap即使用-Xmx来指定JVM可使用的最大堆。而JVM默认会使用它获取到的最大内存的1/4作为堆的原因也是如此。

    安全性(即不会超过容器限制被容器kill)

    OpenJdk

    OpenJdk8-12,都能保证这个安全性的特点(8和9需要特殊参数,-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap)。

    OpenJ9

    2.IbmOpenJ9所有的版本都能识别到容器限制。

    资源利用率

    OpenJdk

    自动识别到容器限制后,OpenJdk把最大堆设置为了大概容器内存的1/4,对内存的浪费不可谓不大。

    当然可以配合另一个JVM参数来配置最大堆。-XX:MaxRAMFraction=int。下面是我整理的一个常见内存设置的表格, 从中我们可以看到似乎JVM默认的最大堆的取值为MaxRAMFraction=4,随着内存的增加,堆的闲置空间越来越大,在16G容器内存时,java堆只有不到4G。关于-XX:MaxRAMFraction=int设置值的介绍 设置=4 就是四分之一,设置=1 就是全部内存。

    MaxRAMFraction
    取值    堆占比 容器内存=1G 容器内存=2G 容器内存=4G 容器内存=8G 容器内存=16G
    1    ≈90%    910.50M 1.78G   3.56G   7.11G   14.22G
    2    ≈50%    455.50M 910.50M 1.78G   3.56G   7.11G
    3    ≈33%    304.00M 608.00M 1.19G   2.37G   4.74G
    4    ≈25%    228.00M 455.50M 910.50M 1.78G   3.56G
    

    OpenJ9

    关于OpenJ9的的详细介绍你可以从这里了解更多。 对于内存利用率OpenJ9的策略是优于OpenJdk的。以下是OpenJ9的策略表格

    容器内存<size>    最大Java堆大小
    小于1 GB    50%<size>
    1 GB - 2 GB    <size> - 512 MB
    大于2 GB    大于2 GB
    

    结论

    注意:这里我们说的是容器内存限制,和物理机内存不同

    自动档

    如果你想要的是,不显示的指定-Xmx,让Java进程自动的发现容器限制。

    1.如果你想要的是jvm进程在容器中安全稳定的运行,不被容器kill,并且你的JDK版本小于10(大于等于JDK10的版本不需要设置,参考前面的测试) 你需要额外设置JVM参数-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap,即可保证你的Java进程不会因为内存问题被容器Kill。 当然这个方式使用起来简单,可靠,缺点也很明显,资源利用率过低(参考前面的表格MaxRAMFraction=4)。

    2.如果想在基础上我还想提高一些内存资源利用率,并且容器内存为1 GB - 4 GB,我建议你设置-XX:MaxRAMFraction=2,在大于8G的可以尝试设置-XX:MaxRAMFraction=1(参考上表格)。

    手动挡

    如果你想要的是手动挡的体验,更加进一步的利用内存资源,那么你可能需要回到手动配置时代-Xmx。 手动挡部分,请可以完全忽略上面我的BB。

    1.上面的我们说到了自动挡的配置,用起来很简单很舒服,自动发现容器限制,无需担心和思考去配置-Xmx。

    2.比如你有内存1G那么我建议你的-Xmx750M,2G建议配置-Xmx1700M,4G建议配置-Xmx3500-3700M,8G建议设置-Xmx7500-7600M, 总之就是至少保留300M以上的内存留给JVM的其他内存。如果堆特别大,可以预留到1G甚至2G。

    3.手动挡用起来就没有那么舒服了,当然资源利用率相对而言就更高了。

    相关文章

      网友评论

          本文标题:JAVA: 容器中的JVM资源该如何被安全的限制?

          本文链接:https://www.haomeiwen.com/subject/lhveaqtx.html