原文 https://www.jianshu.com/p/0897d0581872

背景:众所周知,当我们执行没有任何调优参数(如“java-jar mypplication-fat.jar”)的 Java 应用程序时,JVM 会自动调整几个参数,以便在执行环境中具有最佳性能。

但是许多开发者发现,如果让 JVM ergonomics (即JVM人体工程学,用于自动选择和行为调整)对垃圾收集器、堆大小和运行编译器使用默认设置值,运行在 Linux 容器(docker,rkt,runC,lxcfs 等)中的 Java 进程会与我们的预期表现严重不符。

本篇文章采用简单的方法来向开发人员展示在 Linux 容器中打包 Java 应用程序时应该知道什么。

懒人超精简阅读版:

a.JVM 做不了内存限制,一旦超出资源限制,容器就会出错

b.即使你多给些内存资源,也没什么卵用,只会错上加错

c.解决方案:用 Dockfile 中的环境变量来定义 JVM 的额外参数

d.更进一步:使用由 Fabric8 社区提供的基础 Docker 镜像来定义 Java 应用程序,将始终根据容器调整堆大小

详细全文:

我们往往把容器当虚拟机,让它定义一些虚拟 CPU 和虚拟内存。其实容器更像是一种隔离机制:它可以让一个进程中的资源(CPU,内存,文件系统,网络等)与另一个进程中的资源完全隔离。Linux 内核中的 cgroups 功能用于实现这种隔离。

然而,一些从执行环境收集信息的应用程序已经在 cgroups 存在之前就被执行了。“top”,“free”,“ps”,甚至 JVM 等工具都没有针对在容器内执行高度受限的 Linux 进程进行优化。

1.存在的问题

为了演示,我用“docker-machine create -d virtualbox –virtualbox-memory ‘1024’ docker1024”在1GB RAM的虚拟机中创建了 docker daemon。接下来,在一个虚拟内存为100MB的容器里面跑三个不同的Linux distribution,执行 “free -h”命令,结果是:它们都显示了995MB的总内存。

 
 

即使在 Kubernetes / OpenShift 集群中,结果也类似。

我在一个15GB内存的集群中跑一个 Kubernetes Pod ,并将 Pod 的内存限制为512M (通过“kubectl run mycentos –image=centos -it –limits=’memory=512Mi'”命令实现),最后显示的总内存却是14GB

 
 

如果想知道为什么会发生这种情况,建议您阅读博客“Memoryinside Linux containers – Or why don’t free and top work in a Linux container?”(https://fabiokung.com/2014/03/13/memory-inside-linux-containers/)

docker switches(-m,-memory和-memory-swap)和kubernetes switch(–limits)在进程超过限制的情况下,会指示Linux内核杀死该进程;但JVM是完全不知道限制,所以在进程超过限制的时候,糟糕的事情就发生了!

为了模拟在超过指定的内存限制后被杀死的进程,我们可以通过“docker run -it –name mywildfly -m=50m jboss/wildfly”命令在50MB内存限制的容器中跑WildFly应用server,用“dockerstats”命令来检查容器限制。

 
 

但是在几秒钟之后,Wildfly的容器执行将被中断并显示:*** JBossAS process (55) received KILL signal ***

“docker inspect mywildfly -f ‘{{json.State}}'”命令显示由于OOM(内存不足),该容器已被杀死。注意容器“state”中的OOMKilled = true。

 
 

2.JAVA的应用程序是如何被影响的?

在docker daemon里用Dockerfile中定义的参数-XX:+ PrintFlagsFinal和-XX:+ PrintGCDetails起一个java应用。

其中 machine:1GB RAM容器内存:限制为150M(对于这个Spring Boot应用,似乎够用)

这些参数允许我们读取初始JVM人机工程学参数,并了解有关垃圾收集(GC)执行的详细信息。

动手试一下:

 
 

我已经在“/ api / memory /”上准备了一个端点,它使用String对象加载JVM内存来模拟消耗大量内存的操作。我们来调用一次:

 
 

此端点将回复“分配超过80%(219.8 MiB)的最大允许JVM内存大小(241.7 MiB)”

在这里我们可以提至少两个问题:

为什么JVM最大允许内存241.7 MiB?

如果这个容器将内存限制为150MB,那为什么它允许Java分配近220MB?

首先,我们需要回顾一下JVM人机工程学页面上关于“最大堆大小”的内容:是物理内存的1/4。由于JVM不知道它在一个容器内执行,所以允许最大堆大小将接近260MB。鉴于我们在容器初始化期间添加了-XX:+ PrintFlagsFinal标志,我们可以检查这个值:

 
 

其次,我们需要了解,当我们在docker命令行中使用参数“-m 150M”时,docker daemon将在RAM中限制150M,在Swap中限制为150M。因此,该过程可以分配300M。这就解释了为什么我们的进程没有被杀死。

docker命令行中的内存限制(-memory)和swap(-memory-swap)之间的更多组合可以在这里(https://docs.docker.com/engine/reference/run/#example-run-htop-inside-a-container)找到。

3.提供更多内存是否靠谱?

不了解问题的开发者往往认为环境不能为执行JVM提供足够的内存。所以通常的解决办法是提供更多内存,这实际上会使事情变得更糟。

我们假设将daemon从1GB更改为8GB(使用“docker-machinecreate -d virtualbox –virtualbox-memory ‘8192’ docker8192”创建),并将容器内存从150M更改为800M:

 
 

请注意这次,“curl http://`docker-machine ipdocker8192`:8080/api/memory”命令甚至没有执行完,因为在8GB环境中计算的JVM的MaxHeapSize为2092957696字节(〜2GB)。检查“docker logs mycontainer|grep -i MaxHeapSize”

 
 

该应用将尝试分配超过1.6GB的内存,这超出了此容器的限制(RAM中的800MB + Swap中的800MB),并且该进程将被杀掉。

很显然,用增加内存且让JVM自定义参数的方式在容器里跑Java,不是什么好主意在容器内部运行Java应用程序时,我们应该根据应用程序需求和容器限制设置最大堆大小(-Xmx参数)。

4.解决方案

Dockerfile的一个细微变化允许用户指定一个环境变量来定义JVM的额外参数。检查以下行:

 
 

现在我们可以使用JAVA_OPTIONS环境变量来通知JVM堆的大小。对于这个应用程序,300M就够了。稍后可以检查日志并获取314572800字节(300MBi)的值

对于docker,您可以使用“-e”switch指定环境变量。

 
 

在Kubernetes中,您可以使用switch“-env = [key = value]”设置环境变量:

 
 

再进一步

如果可以根据容器限制自动计算堆的值,该怎么做?

使用由Fabric8社区提供的基础Docker镜像,就可以搞定。这个镜像fabric8 / java-jboss-openjdk8-jdk使用一个脚本来计算容器限制,并使用50%的可用内存作为上限。请注意,这个50%的内存比可以被复写。您还可以使用此镜像来启用/禁用调试,诊断等。

 
 

下面一起看看Dockerfile是如何作用于这个Spring Boot应用程序:

搞定!现在,无论容器内存限制是多少,我们的Java应用程序将始终根据容器调整堆大小,而不是根据daemon调整堆大小。

 
 

5.结论

直到现在,Java JVM依然没有提供什么支持,让大家可以理解它在容器内是如何运行的,而且它有一些资源是内存和CPU限制的。因此,您不能让JVM人体工程学本身决定最大堆大小。

解决此问题的一种方法是使用能够理解它在受限容器内运行的Fabric8 Base镜像

在JVM中有一个实验支持,已经包含在JDK9中以支持容器(即Docker)环境中的cgroup内存限制。可以参考:http://hg.openjdk.java.net/jdk9/jdk9/hotspot/rev/5f1d1df0ea49

原文评论:更好的方法是以exec表单定义您的CMD指令,这将确保java是PID 1进程-这对于允许Java在容器停止时正常关闭至关重要。

Exec表单不支持环境变量替换,但您可以通过设置JAVA_TOOL_OPTIONS环境变量来传递其他命令行标志(请参阅http://bit.ly/2mTIDUt

作者:Tenxcloud
链接:https://www.jianshu.com/p/0897d0581872
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

在 Docker 里跑 Java,你必须知道的那些事儿!(转)的更多相关文章

  1. MySQL 到底能不能放到 Docker 里跑?

    https://weibo.com/ttarticle/p/show?id=2309404296528549285581 前言 前几月经常看到有 MySQL 到底能不能放到 Docker 里跑的各种讨 ...

  2. 在docker里查看java进程

    先使用命令查看docker的运行进程 docker ps [root@localhost logs]# docker ps CONTAINER ID        IMAGE             ...

  3. jenkins和docker 在docker里运行jenkins

    在docker里运行jenkins server. 文章来自:http://www.ciandcd.com文中的代码来自可以从github下载: https://github.com/ciandcd ...

  4. 从容器里dump java堆实验探索(原创)

    目标:从docker容器里dump java堆 模拟程序 占用空间500M, 设置启动JVM参数 docker启动命令 (PS:经过测试,至少要650M才能启动容器) 方式1: 通过docker ex ...

  5. 在Docker中监控Java应用程序的5个方法

    译者注:Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化.通常情况下,监控的主要目的在于:减少宕机 ...

  6. [转帖]Docker里运行Docker docker in docker(dind)

    Docker里运行Docker docker in docker(dind) http://www.wantchalk.com/c/devops/docker/2017/05/24/docker-in ...

  7. 在Docker里使用(支持镜像继承的)supervisor管理进程(转)

    这篇文章是受 dockboard 之托帮忙翻译的与 docker 有关的技术文章.译自 Using Supervisor with Docker to manage processes (suppor ...

  8. 在docker里部署网络服务

    之前试着玩玩docker有一阵子了,今天算是头一回正式在docker里部署网络服务. 本来想和lxc差不多的东西那自然是手到擒来,没想到还是改了很多. 第一个遇到的问题是,远程连到docker宿主机干 ...

  9. 在ORACLE触发器里调用JAVA程序

    因为项目需要,有一个已经写好的Java程序,想要在Oracle某个表的触发器中调用,以使得每次数据更新时,调用这个JAVA程序,来修改后台某个数据. 现将过程记录如下: 1.编写JAVA程序 publ ...

随机推荐

  1. ubuntu 14.04 重装机 安装笔记 无线网卡+cuda+nvidia

    1. 安装QA6714 无线网卡重要参考网页 #22 回答 https://bugs.launchpad.net/ubuntu/+source/linux-firmware/+bug/1520343? ...

  2. 1077 Eight

    Eight Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 37738   Accepted: 15932   Special ...

  3. Linux 环境下安装Redis的步骤

    #进入usr/local目录cd /usr/local#下载1.wget http://download.redis.io/releases/redis-4.0.10.tar.gz#解压2.tar x ...

  4. 3.14 unittest之skip

    3.14 unittest之skip 前言当测试用例写完后,有些模块有改动时候,会影响到部分用例的执行,这个时候我们希望暂时跳过这些用例.或者前面某个功能运行失败了,后面的几个用例是依赖于这个功能的用 ...

  5. [亲身实践]linux命令行下配置网路

    1.在命令行下输入setup, 2.之后出现下图,选择网络配置 4.配置IP地址,子网掩码,DNS 5.保存之后回到命令行模式下,输入service network restart,至此网络配置完成

  6. where 常用条件范例

    where() public method Sets the WHERE part of the query. The method requires a $condition parameter, ...

  7. 安装vmware tools问题

    我爱破解的xp虚拟机,之前没有装vmware tools,用起来非常不方便.因此,决定安装,但安装时出现了一些问题,特此记录: * 点击虚拟机的安装VMWARE TOOLS ,出现错误提示: 虚拟机需 ...

  8. [2019BUAA软件工程]结对作业

    Tips Link 作业链接 [2019BUAA软件工程]结对作业 GitHub地址 WordChain PSP表格 psp2.1   预估耗时(分钟) 实际耗时(分钟) Planning 计划 60 ...

  9. SQL函数语句

    MyBatis实现模糊查询 1.${-}代替#{-} 2.把'%#{name}%'改为"%"#{name}"%" 3.使用sql中的字符串拼接函数 4.使用标签 ...

  10. MySQL中MyISAM与InnoDB的主要区别对比

    特征 MyISAM InnoDB 聚集索引 否 是 压缩数据 是(仅当使用压缩行格式时才支持压缩MyISAM表.使用压缩行格式和MyISAM的表是只读的.) 是 数据缓存 否 是 加密数据 是(通过加 ...