前言

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

  1. cat /proc/meminfo

  2. MemTotal:       197869260 kB

  3. MemFree:         3698100 kB

  4. MemAvailable:   62230260 kB

容器

  1. docker run -it --rm alpine cat /proc/meminfo

  2. MemTotal:       197869260 kB

  3. MemFree:         3677800 kB

  4. 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的物理机。

  1. #free -g

  2.              total        used        free      shared  buff/cache   available

  3. Mem:            188         122           1           0          64          64

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

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

测试用例1(OPENJDK)

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

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

  1. docker run -m 4GB  --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version

  2. docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version

  3. docker run -m 4GB --rm  openjdk:10-jre-slim java -XshowSettings:vm  -version

  4. docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  -version

  5. docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java  -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 26.67G

  4.    Ergonomics Machine Class: server

  5.    Using VM: OpenJDK 64-Bit Server VM

  6. openjdk version "1.8.0_181"

  7. OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)

  8. OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:8-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 910.50M

  4.    Ergonomics Machine Class: server

  5.    Using VM: OpenJDK 64-Bit Server VM

  6. openjdk version "1.8.0_181"

  7. OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)

  8. OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java  -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 29.97G

  4.    Using VM: OpenJDK 64-Bit Server VM

  5. openjdk version "9.0.4"

  6. OpenJDK Runtime Environment (build 9.0.4+12-Debian-4)

  7. OpenJDK 64-Bit Server VM (build 9.0.4+12-Debian-4, mixed mode)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:9-jre-slim java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM

  5. openjdk version "9.0.4"

  6. OpenJDK Runtime Environment (build 9.0.4+12-Debian-4)

  7. OpenJDK 64-Bit Server VM (build 9.0.4+12-Debian-4, mixed mode)

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

  1. [root@xiaoke-test ~]# docker run -m 32GB --rm  openjdk:10-jre-slim java -XshowSettings:vm -XX:MaxRAMFraction=1  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM

  5. openjdk version "10.0.2" 2018-07-17

  6. OpenJDK Runtime Environment (build 10.0.2+13-Debian-2)

  7. OpenJDK 64-Bit Server VM (build 10.0.2+13-Debian-2, mixed mode)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:11-jre-slim java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM

  5. openjdk version "11.0.1" 2018-10-16

  6. OpenJDK Runtime Environment (build 11.0.1+13-Debian-3)

  7. OpenJDK 64-Bit Server VM (build 11.0.1+13-Debian-3, mixed mode, sharing)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  openjdk:12 java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 1.00G

  4.    Using VM: OpenJDK 64-Bit Server VM

  5. openjdk version "12-ea" 2019-03-19

  6. OpenJDK Runtime Environment (build 12-ea+23)

  7. OpenJDK 64-Bit Server VM (build 12-ea+23, mixed mode, sharing)

测试用例2(IBMOPENJ9)

  1. docker run -m 4GB --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. docker run -m 4GB --rm  adoptopenjdk/openjdk9-openj9:alpine-slim  java -XshowSettings:vm  -version

  3. docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java -XshowSettings:vm  -version

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

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk8-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Ergonomics Machine Class: server

  5.    Using VM: Eclipse OpenJ9 VM

  6. openjdk version "1.8.0_192"

  7. OpenJDK Runtime Environment (build 1.8.0_192-b12_openj9)

  8. Eclipse OpenJ9 VM (build openj9-0.11.0, JRE 1.8.0 Linux amd64-64-Bit Compressed References 20181107_95 (JIT enabled, AOT enabled)

  9. OpenJ9   - 090ff9dcd

  10. OMR      - ea548a66

  11. JCL      - b5a3affe73 based on jdk8u192-b12)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk9-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Using VM: Eclipse OpenJ9 VM

  5. openjdk version "9.0.4-adoptopenjdk"

  6. OpenJDK Runtime Environment (build 9.0.4-adoptopenjdk+12)

  7. Eclipse OpenJ9 VM (build openj9-0.9.0, JRE 9 Linux amd64-64-Bit Compressed References 20180814_248 (JIT enabled, AOT enabled)

  8. OpenJ9   - 24e53631

  9. OMR      - fad6bf6e

  10. JCL      - feec4d2ae based on jdk-9.0.4+12)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk10-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Using VM: Eclipse OpenJ9 VM

  5. openjdk version "10.0.2-adoptopenjdk" 2018-07-17

  6. OpenJDK Runtime Environment (build 10.0.2-adoptopenjdk+13)

  7. Eclipse OpenJ9 VM (build openj9-0.9.0, JRE 10 Linux amd64-64-Bit Compressed References 20180813_102 (JIT enabled, AOT enabled)

  8. OpenJ9   - 24e53631

  9. OMR      - fad6bf6e

  10. JCL      - 7db90eda56 based on jdk-10.0.2+13)

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

  1. [root@xiaoke-test ~]# docker run -m 4GB --rm  adoptopenjdk/openjdk11-openj9:alpine-slim  java -XshowSettings:vm  -version

  2. VM settings:

  3.    Max. Heap Size (Estimated): 3.00G

  4.    Using VM: Eclipse OpenJ9 VM

  5. openjdk version "11.0.1" 2018-10-16

  6. OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.1+13)

  7. Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.11.0, JRE 11 Linux amd64-64-Bit Compressed References 20181020_70 (JIT enabled, AOT enabled)

  8. OpenJ9   - 090ff9dc

  9. OMR      - ea548a66

  10. JCL      - f62696f378 based on jdk-11.0.1+13)

分析

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

  1. 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。

  1. MaxRAMFraction取值    堆占比 容器内存=1G 容器内存=2G 容器内存=4G 容器内存=8G 容器内存=16G

  2. 1    ≈90%    910.50M 1.78G   3.56G   7.11G   14.22G

  3. 2    ≈50%    455.50M 910.50M 1.78G   3.56G   7.11G

  4. 3    ≈33%    304.00M 608.00M 1.19G   2.37G   4.74G

  5. 4    ≈25%    228.00M 455.50M 910.50M 1.78G   3.56G

OpenJ9

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

  1. 容器内存<size>    最大Java堆大小

  2. 小于1 GB    50%<size>

  3. 1 GB - 2 GB    <size> - 512 MB

  4. 大于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.手动挡用起来就没有那么舒服了,当然资源利用率相对而言就更高了。

原文链接:

https://qingmu.io/2018/12/17/How-to-securely-limit-JVM-resources-in-a-container/

- MORE | 往期精彩文章 -

容器中的JVM资源该如何被安全的限制?的更多相关文章

  1. 【原创】大叔经验分享(71)docker容器中使用jvm工具

    java应用中经常需要用到jvm工具来进行一些操作,如果java应用部署在docker容器中,如何使用jvm工具? 首先要看使用的docker镜像, 比如常用的openjdk镜像分为jdk和jre,只 ...

  2. Docker 镜像,dump openjdk-alpine 镜像容器中的 jvm

    默认情况下,我们使用的都是 jre 版本的 openjdk,当容器启动卡住不动的时候,看不出来任何问题. 此时如果能 dump 就能知道线程在干啥,也能找到一些大概的问题. 此时 jre 版本的镜像就 ...

  3. 通过JNDI从服务器容器中获取资源_Spring JNDI+Mysql+Tomcat

    通过JNDI从服务器容器中获取DataSource资源 (由容器管理,不要关闭它,容器自己会处理)上一篇我们使用的是dbcp,这里使用JNDI: 使用JNDI连接数据: 在Spring可以注释 < ...

  4. 在Docker容器中搭建MXNet/Gluon开发环境

    在这篇文章中没有直接使用MXNet官方提供的docker image,而是从一个干净的nvidia/cuda镜像开始,一步一步部署mxnet需要的相关软件环境,这样做是为了更加细致的了解mxnet的运 ...

  5. 容器中JVM获取真实的CPU核数

    容器中JVM获取真实的CPU核数 基于 libsysconfcpus的方案,可以为各个版本的JDK提供一个通用的解决方案. libsysconfcpus.so的原理是截获JVM获取CPU核数所用的系统 ...

  6. 容器内部设置JVM的Heap大小

    容器内部利用脚本来获取容器的CGroup资源限制,并通过设置JVM的Heap大小. Docker1.7开始将容器cgroup信息挂载到容器中,所以应用可以从 /sys/fs/cgroup/memory ...

  7. 如何设置Docker容器中Java应用的内存限制

    如果使用官方的Java镜像,或者基于Java镜像构建的Docker镜像,都可以通过传递 JAVA_OPTS 环境变量来轻松地设置JVM的内存参数.比如,对于官方Tomcat 镜像,我们可以执行下面命令 ...

  8. 【String注解驱动开发】如何按照条件向Spring容器中注册bean?这次我懂了!!

    写在前面 当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,不 ...

  9. Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?

    阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...

随机推荐

  1. 为 docker 中的 nginx 配置 https

    没有 https 加持的网站会逐渐地被浏览器标记为不安全的,所以为网站添加 https 已经变得刻不容缓.对于商业网站来说,花钱购买 SSL/TLS 证书并不是什么问题.但对于个人用户来说,如果能有免 ...

  2. Supervisor 为服务创建守护进程

    今天需要再服务上部署一个.net 方面的项目:当时开启服务的命令只能在前台执行:使用nohub CMD &等放在后台开启服务都会宕机:所以搜寻了Supervisor 这个解决办法,为服务创建守 ...

  3. NiftyNet 数据预处理

    NiftyNet项目介绍 使用NiftyNet时,我们需要先将图像数据和标签进行一次简单的处理,得到对应的.csv文件. 对应文件格式为: img.csv image path img_name im ...

  4. 痞子衡嵌入式:飞思卡尔i.MX RT系列MCU启动那些事(2)- Boot配置(BOOT Pin/eFUSE)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Boot配置. 在上一篇文章 Boot简介 里痞子衡为大家介绍了Boot基本原理以及i.MXRT Bo ...

  5. MySQL/MariaDB系列文章目录

    以下是本系列文章的大纲,此页博文完全原创,花费了作者本人的极大心血,如转载,请务必标明原文链接. 如果觉得文章不错,还请帮忙点下"推荐",各位的支持,能激发和鼓励我更大的写作热情. ...

  6. C语言之递归

    递归例子如下: #include <stdio.h> /*函数声明*/ void digui(int n); int main() { ; digui(n); ; } void digui ...

  7. Android安全–检测是否为Android模拟器

    有时候需要检测是否在模拟器上运行还是在真机运行,话不多说.检测代码如下: package com.monkey.antiemulator; import java.io.File; import ja ...

  8. 使用Jenkins自动发布Windows服务项目

    不同于发布Web项目,自动发布Windows服务项目需要解决以下几个问题: 如何远程停止和开启服务?需要在发布前停止服务,在发布完成后开启服务. 如何上传编译文件到目标服务器? 问题1:如何远程停止和 ...

  9. Dynamics 365支持的语言(中文语言名/英文语言名)列表

    本人微信和易信公众号:微软动态CRM专家罗勇 ,回复277或者20180803可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong.me ...

  10. 随意下载:afinal jar

    刚才用到afinal,下载jar包到处要积分啥的,我花钱下载了,现在免费给兄弟们 链接: https://pan.baidu.com/s/1BmB-06ui1D2BfooUCi6xKg 提取码: m3 ...