当前位置: 首页 > Linux

容器中的JVM资源应该如何安全限制?

时间:2023-04-06 18:25:02 Linux

前言Java和Docker的结合,虽然更好的解决了应用打包的问题。但是也有不兼容的地方。例如,Java无法自动发现Docker设置的内存限制和CPU限制。这样就会导致JVM无法稳定的为业务服务!容器会杀死你的JVM进程,健康检查会拉起你的JVM进程,这会导致你每天甚至数百次监控你的pod重启。我们希望Java进程在容器中运行时,Java能够自动识别容器限制并获取正确的内存和CPU信息,而不是每次都需要配置kubernetesyaml描述文件中显示的容器,还需要配置JVM参数。使用JVMMaxRAM参数或者解锁实验特性的JVM参数,将JDK升级到10+,就可以解决这个问题(也许~.~)。首先,Docker容器本质上是宿主机上的一个进程,它与宿主机共享一个/proc目录,也就是说我们在容器中看到的/proc/meminfo和/proc/cpuinfo是一样的直接在host上看到的协议,如下。Hostcat/proc/meminfoMemTotal:197869260kBMemFree:3698100kBMemAvailable:62230260kBContainerdockerrun-it--rmalpinecat/proc/meminfoMemTotal:197869260kBMemFree:3677800kBMemAvailable:62210内存信息呢?没错,是通过/proc/meminfo获取的。默认情况下,JVM的最大堆大小是系统内存的1/4。如果我们的系统是8G,那么JVM默认的Heap≈2G。Docker通过CGroups完成内存限制,/proc目录以只读形式挂载在容器中。由于Java默认情况下根本看不到CGroups限制的内存大小,因此它默认使用/proc/meminfo。中的信息作为内存信息启动。这种不兼容会导致,如果容器分配的内存小于JVM的内存,JVM进程就会被理解并杀死。内存限制不兼容。我们先来看一组测试。这里我们使用内存为188G的物理机。#free-gtotalusedusedfreesharedbuff/cacheavailableMem:1881221064在下面的测试中,我们将包括openjdk的热点虚拟机和IBM的openj9虚拟机。在下面的测试中,我们将正确识别出limit的jdk称为safe(即不会超过containerlimit,不会被kill),否则称为dangerous。对于测试用例1(OPENJDK),我们使用最新的openjdk8-12,将容器的内存限制为4G。JDK默认参数下的最大堆是多少?查看我们默认参数下有多少个版本的JDK是安全的命令如下。如果你想尝试一下,你可以使用命令。dockerrun-m4GB--rmopenjdk:8-jre-slimjava-XshowSettings:vm-versiondockerrun-m4GB--rmopenjdk:9-jre-slimjava-XshowSettings:vm-versiondockerrun-m4GB--rmopenjdk:10-jre-slimjava-XshowSettings:vm-versiondockerrun-m4GB--rmopenjdk:11-jre-slimjava-XshowSettings:vm-versiondockerrun-m4GB--rmopenjdk:12java-XshowSettings:vm-versionOpenJDK8(并没有密码器限制,26.67G)危险[root@xiaoke-test~]#dockerrun-m4GB--rmopenjdk:8-jre-slimjava-XshowSettings:vm-versionVM设置:最大堆大小(估计):26.67G人体工程学机器类:服务器使用VM:OpenJDK64位服务器VMopenjdk版本“1.8.0_181”OpenJDK运行时环境(构建1.8.0_181-8u181-b13-2~deb9u1-b13)OpenJDK64-BitServerVM(build25.181-b13,mixedmode)OpenJDK8-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap(正确的密码器限制,910.50M)安全[root@xiaoke-test~]#dockerrun-m4GB--rmopenjdk:8-jre-slimjava-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap-XshowSettings:vm-versionVM设置:最大。堆大小(估计):910.50MErgonomics机器类:服务器使用VM:OpenJDK64位服务器VM`openjdk版本“1.8.0_181”OpenJDK运行时环境(构建1.8.0_181-8u181-b13-2~deb9u1-b13)OpenJDK64-BitServerVM(build25.181-b13,mixedmode)OpenJDK9(并没有其他容器限制,26.67G)危险[root@xiaoke-test~]#dockerrun-m4GB--rmopenjdk:9-jre-slimjava-XshowSettings:vm-versionVM设置:最大。堆大小(估计):29.97G使用VM:OpenJDK64位服务器VMopenjdkversion"9.0.4"OpenJDKRuntimeEnvironment(build9.0.4+12-Debian-4)OpenJDK64-BitServerVM(build9.0.4+12-Debian-4,混合模式)OpenJDK9-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap(正确的密码器限制,1G)安全[root@xiaoke-test~]#dockerrun-m4GB--rmopenjdk:9-jre-slimjava-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap-XshowSettings:vm-versionVM设置:最大。堆大小(估计):1.00G使用VM:OpenJDK64位服务器VMopenjdk版本“9.0.4”OpenJDK运行时环境(build9.0.4+12-Debian-4)OpenJDK64位服务器VM(build9.0.4+12-Debian-4,混合模式)OpenJDK10(正确的密码器限制,1G)安全[root@xiaoke-test~]#dockerrun-m32GB--rmopenjdk:10-jre-slimjava-XshowSettings:vm-XX:MaxRAMFraction=1-version虚拟机设置:最大。堆大小(估计):1.00G使用VM:OpenJDK64位服务器VMopenjdk版本“10.0.2”2018-07-17OpenJDK运行时环境(构建10.0.2+13-Debian-2)OpenJDK64位服务器VM(build10.0.2+13-Debian-2,mixedmode)OpenJDK11(正确的密码器限制,1G)安全[root@xiaoke-test~]#dockerrun-m4GB--rmopenjdk:11-jre-slimjava-XshowSettings:vm-versionVMsettings:Max.堆大小(估计):1.00G使用VM:OpenJDK64位服务器VMopenjdk版本“11.0.1”2018-10-16OpenJDKRuntimeEnvironment(build11.0.1+13-Debian-3)OpenJDK64-BitServerVM(build11.0.1+13-Debian-3,mixedmode,sharing)OpenJDK12(correctlyrecognizecontainerlimit,1G)security[root@xiaoke-test~]#dockerrun-m4GB--rmopenjdk:12java-XshowSettings:vm-version虚拟机设置:最大。堆大小(估计):1.00G使用VM:OpenJDK64位服务器VMopenjdk版本“12-ea”2019-03-19OpenJDK运行时环境(构建12-ea+23)OpenJDK64位服务器VM(构建12-ea+23,混合模式,共享)测试用例2(IBMOPENJ9)dockerrun-m4GB--rmadoptopenjdk/openjdk8-openj9:alpine-slimjava-XshowSettings:vm-versiondockerrun-m4GB--rmadoptopenjdk/openjdk9-openj9:alpine-slimjava-XshowSettings:vm-versiondockerrun-m4GB--rmadoptopenjdk/openjdk11-openj9:alpine-slimjava-XshowSettings:vm-versionopenjdk8-openj9(正确识别容器限制,3G)Security[root@小客测试~]#dockerrun-m4GB--rmadoptopenjdk/openjdk8-openj9:alpine-slimjava-XshowSettings:vm-versionVM设置:最大。堆大小(估计):3.00G人体工程学机器类:服务器使用VM:EclipseOpenJ9VMopenjdk版本“1.8.0_192”OpenJDK运行时环境(构建1.8.0_192-b12_openj9)EclipseOpenJ9VM(构建openj9-0.11.0,JRE1.8.0Linuxamd64-64位压缩引用20181107_95(启用JIT,启用AOT)OpenJ9-090ff9dcdOMR-ea548a66JCL-b5a3affe73basedonjdk8u192-b12)openjdk9-openj9(正确的密码器限制,3G)#rootdoke-test-m4GB--rmadoptopenjdk/openjdk9-openj9:alpine-slimjava-XshowSettings:vm-versionVM设置:最大。堆大小(估计):3.00G使用VM:EclipseOpenJ9VMopenjdk版本“9.0.4-adoptopenjdk”OpenJDK运行时环境(构建9.0.4-adoptopenjdk+12)EclipseOpenJ9VM(构建openj9-0.9.0,JRE9Linuxamd64-64位压缩引用20180814_248(启用JIT,启用AOT)OpenJ9-24e53631OMR-fad6bf6eJCL-feec4d2ae基于jdk-9.0.4+12)openjdk10-openj9(正确识别容器限制,3G)security[root@xiaoke-test~]#dockerrun-m4GB--rmadoptopenjdk/openjdk10-openj9:alpine-slimjava-XshowSettings:vm-versionVM设置:最大。堆大小(估计):3.00GU使用VM:EclipseOpenJ9VMopenjdk版本“10.0.2-adoptopenjdk”2018-07-17OpenJDK运行时环境(build10.0.2-adoptopenjdk+13)EclipseOpenJ9VM(buildopenj9-0.9.0,JRE10Linuxamd64-64-BitCompressedReferences20180813_102(JITenabled,AOTenabled)OpenJ9-24e53631OMR-fad6bf6eJCL-7db90eda56basedonjdk-10.0.2+13)openjdk11-openj9(正确识别容器限制,3G)security[root@xiaoke-test~]#dockerrun-m4GB--rmadoptopenjdk/openjdk11-openj9:alpine-slimjava-XshowSettings:vm-version虚拟机设置:Max.堆大小(估计):3.00G使用VM:EclipseOpenJ9VMopenjdk版本“11.0.1”2018-10-16OpenJDK运行时环境tAdoptOpenJDK(build11.0.1+13)EclipseOpenJ9VMAdoptOpenJDK(buildopenj9-0.11.0,JRE11Linuxamd64-64-BitCompressedReferences20181020_70(JITenabled,AOTenabled)OpenJ9-090ff9dcOMR-ea548a66JCL-基于f62696f378onjdk-11.0.1+13)分析分析之前先了解这样一种情况:JavaMemory(MaxRAM)=元数据+线程+代码缓存+OffHeap+Heap...一般我们只配置Heap,即使用-Xmx来指定JVM默认会使用它所能获得的最大内存的1/4作为堆的原因也是如此。安全性(即不会超过容器限制而被容器杀死)OpenJdkOpenJdk8-12可以保证这个安全特性(8和9需要特殊参数,-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap)。OpenJ92.IbmOpenJ9的所有版本都知道容器限制。资源利用OpenJdk自动识别容器限制后,OpenJdk将最大堆设置为容器内存的1/4左右,不会造成很大的内存浪费。当然,您可以使用另一个JVM参数配置最大堆。-XX:MaxRAMFraction=整数。下面是我整理的常用内存设置表。从中我们可以看出,JVM默认的最大堆值是MaxRAMFraction=4。随着内存的增加,堆的空闲空间越来越大。在16G容器内存的时候,java堆还不到4G。MaxRAMFractionHeapratio容器内存=1G容器内存=2G容器内存=4G容器内存=8G容器内存=16G`1≈90%910.50M1.78G3.56G7.11G14.22G2≈50%455.50M910.50M1.78G3.56G7.11G3≈33%304.00M608.00M1.19G2.37G4.74G4≈25%228.00M455.50M910.50M1.78G3.56GOpenJ9您可以从此处了解有关OpenJ9的更多信息。OpenJ9对内存利用的策略优于OpenJdk。下面是OpenJ9容器内存的策略表最大Java堆大小小于1GB50%1GB-2GB-512MB大于2GB大于2GB结论注:这里我们说的是容器内存限制,不像物理机内存,如果要自动文件,指定-Xmx不显示,这样Java进程可以自动发现容器限制。1、如果你希望jvm进程在容器中安全稳定运行,不被容器杀死,并且你的JDK版本小于10(大于等于JDK10的版本不需要设置,参考之前的测试)需要额外设置JVM参数-XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap可以保证你的Java进程不会因为内存问题被容器杀死。当然,这种方法使用起来简单可靠,但其缺点也很明显,资源利用率太低(参考上表MaxRAMFraction=4)。2、如果想在基础上提高一些内存资源的利用率,容器内存在1GB-4GB,建议设置-XX:MaxRAMFraction=2,可以尝试设置-XX:MaxRAMFraction=如果它大于8G1(请参阅上表)。手动传输如果你想要手动传输的体验,进一步使用内存资源,那么你可能需要回到手动配置的时代——Xmx。手动挡部分,请完全无视我上面的BB。1、上面我们说了自动传输的配置,用起来很简单舒服,自动发现容器限制,不用担心和想配置-Xmx。2、比如你内存1G,我建议你使用-Xmx750M,2G建议配置-Xmx1700M,4G建议配置-Xmx3500-3700M,8G建议设置-Xmx7500-7600M。总之,至少要为其他JVMMemory预留300M以上的内存。如果heap特别大,可以预留到1G甚至2G。3.用手动传输不是那么舒服,当然资源利用率相对要高一些。原文:https://qingmu.io/2018/12/17/...