在rancher中部署完java应用之后,需要对java程序的jvm进行设置,这个非常重要,不然可能会引起比较严重的后果:容器无限制的重启或者主机的内存被耗尽。
在开始之前,先来看一个问题:
在容器中跑了一个java应用,那怎么来限制这个jvm的memory呢?
按照传统的思路对memory进行限制:
首先java应用的jvm内存限制可以通过-Xmx
进行限制,容器的内存限制也是可以设置的,特别是对于kubernetes的容器,可以通过resource request/limit
来设置一个memory可以使用的范围。
如此一来,jvm中的memory如果只设定成一个固定的数值就显得非常不灵活了。如果jvm可以自动的识别容器的可用memory的话就好了,这样的话,如果我希望调大jvm内存,只需要改变容器的memory limit
的定义就可以了,不用再调整java应用的jvm参数值了。
带着这个问题,继续往下看。
一、设置容器的限制
首先明确一点:不要为在容器中运行的任何java程序手动设置jvm堆空间,而是要设置容器的限制。
那到底是为什么呢?
- 首先,设置容器限制可以实现容器(cgroups)的基本目标:隔离进程集合的资源使用。例如,当通过JVM参数手动分配空间时,可能完全忽视了容器的限制。
- 它允许轻松调整容器的资源分配。例如,应用需要更多的内存的时候,我们只需要把容器的资源限制调大就行,而无需修改容器中JVM的参数。
- 在容器编排环境(例如Kubernetes)中运行,则容器限制对于节点运行状况和调度都将变得极为重要。调度程序将使用这些限制来找到合适的节点来运行容器,并确保相等的负载分布在各个节点上。如果通过JVM参数设置内存使用率,则此信息对于调度程序不可用,因此调度程序不知道如何有效的分散容器的负载。
- 如果未设置容器限制,并且Java应用在没有显式设置任何JVM内存标志的容器中运行,则JVM会自动将最大堆设置为运行它的主机节点上RAM的1/4,。例如,如果容器在32G内存的主机节点上运行,则JVM进程堆空间可以最大为8G。如果在一个节点上运行10个容器,最大可能需要80G的堆内存,这样造成的后果就可想而知了。
二、设置JVM的限制
在容器环境中,由于java获取不到容器的内存限制,只能获取到服务器的配置,所以这样容易引起不必要的问题。例如,限制容器使用100M内存,但是jvm根据服务器配置来分配拟初始化内存,导致java进程超过容器限制被kill掉。为了解决这个问题,我们可以设置-Xmx
或者MaxRAM
来解决,但是这就跟刚开始讲的一样,非常不灵活。
为了解决这个问题,Java 10
引入了+UseContainerSupport
(默认情况下开启),通过这个特性,可以使得JVM在容器环境中分配合理的堆内存。并且在jdk8u191
版本之后,这个功能引入到了JDK8,这对于jdk8的用户而言确实是一个很大利好,也可以说是大势所趋吧。
-XX:+UseContainerSupport
允许JVM从主机读取cgroup限制,例如可用的CPU和RAM,并进行相应的配置。这样当JVM内存不够用时,会抛出OOM异常,就不会因为JVM超过容器的资源限制而使得容器被杀死了。
注意:在jdk8u191
版本之后,-XX:{Min|Max}RAMFraction
已经被弃用了,引入了-XX:MaxRAMPercentage
,其值介于0.0到100.0直接,默认值为25.0。
三、实践
首先要保证我们的jdk版本是jdk8u191
或者更高版本jdk8。
1、对容器的资源进行限制
这里直接在已有的项目上进行修改,由于UI用的是rancher,可以直接在UI中进行设置:
点击升级
–>显示高级选项
–>安全/主机设置
,按照实际需求填写,我这里限制内存为1024M,限制CPU为1核。
这里还要注意一下:因为对容器的资源进行限制之后,java应用启动所花费的时间会变长,所以如果开启了健康检查
的话,记得把时间改得略微长一点,不然就可能在你之前所设定的时间内服务还没完全启来的话,pod会无限的 重启。
然后点击升级
即可。
2、对JVM资源进行限制
在Dockerfile中,在java应用的启动命令中加入-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0
,如下:
java -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -jar /opt/app.jar --server.port=${port} --spring.profiles.active=${opt_active}
- 1
其中的75.0
表示将JVM的MaxHeapSize(最大堆内存)设置为容器内存的75%。
注意:这里如果设置成75%,要写成-XX:MaxRAMPercentage=75.0
;如果设置成80%要,要写成-XX:MaxRAMPercentage=80.0
。看出端倪了吧,重点就是在这个.0
上。我之前写成75,运行java程序的时候就会报错:Improperly specified VM option 'MaxRAMPercentage=75'
。网上搜索之后,发现这是个bug,需要在后面加上.0
才行,具体见https://stackoverflow.com/questions/58171149/java-8-docker-improperly-specified-vm-option-initialrampercentage-xx
3、验证
完成上面的容器和JVM的资源限制之后,重新发布下这个java应用。
在验证之前,按照理算的话,JVM的最大堆内存应该=容器内存限制值*75%=1024*75%=768M
然后直接用jmap
命令查看JVM的堆内存:
和预期的一样,现在的MaxHeapSize
的值为768M。这样以后要扩大JVM的堆内存的话,就只要修改容器的内存限制值就可以了,MaxHeapSize
的值会随之增大。
参考文章:
https://www.colabug.com/2019/1225/6769764/amp/
https://zhuanlan.zhihu.com/p/140849800
https://www.bbsmax.com/A/qVdeP3qE5P/
https://stackoverflow.com/questions/58171149/java-8-docker-improperly-specified-vm-option-initialrampercentage-xx