Docker作为ServerLess的新的软件架构和场景,Java势必会融入到这个环境之中,那么我们就说一下Java在Docker中的运行
Docker中的内存,CPU等资源限制是用过CGroup进行实现的,JDK并不能识别这些限制
如果没有设置合适的JVM堆和元数据区,直接内存,可能JVM使用超过限制的内存,导致的OOM,如果判断错误了可以获取的CPU资源,Docker限制了CPU核数,JVM就可能设置不合适的GC并行线程
那么Docker这种运行环境,对于Java有什么问题?
先Pass Docker本身的实现,我们说下Docker本身没有隐藏的一些底层信息,
首先CGroup作为新兴的技术,一些老版本的JDK无法理解这些资源限制
而且,namespace对于容器的应用细节有了些微妙的差异,jcmd,jstack等工具,依赖于 “/proc/” 下的部分信息,Docker的设计改变了这部分信息的结构
比如说,Jvm会在启动时候检测内存大小,将初始堆的大小设置为内存上限的 1/64,将对堆的最大值设置为内存的1/4
并且检测系统的CPU核数,影响到了Parllel GC的并发线程数目和JIT complier线程数目,应用中的ForkJoinPool机制的并行等级
由于老版本的JVM可能对这个判断是基于错误信息进行的,所以导致服务的错误
最为简单的解决方案就是升级JDK的版本
JDK9中引入了一些实验性的参数,方便让JDK和Docker沟通
针对内存的限制,可以设置如下参数
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
这两个参数顺序敏感,只支持Linux,这样方便Java知道cgroup的设置
如果是JDK10的环境,问题更加简单了,Java的容器支持已经比较完善,默认适应各种资源限制和实现差异
-XX:+UseCGroupMemoryLimitForHeap 已经被标记为废弃
新增了参数可以指定CPU 核心的数目
-XX:+UseCGroupMemoryLimitForHeap
而且,还提供了关闭容器支持特性,作为一种防御性机制,避免新特性破坏原有基础功能
对于老版本的JDK的设置
我们需要明确设置 堆 元数据区 等内存区域的大小
比如Docker,可以设置对应的启动参数
$ docker run -it –rm –name yourcontainer -p 8080:8080 -m 800M repo/your-java-container:openjdk
配置如下的环境变量,指定JVM堆的大小
-e JAVA_OPTIONS=’-Xmx300m’
明确的设置GC和JIT并行线程数目,
-XX:ParallelGCThreads
-XX:CICompilerCount
然后就是避免使用SWAP,告诉JVM实际能够使用的内存上限
-XX:MaxRAM=`cat /sys/fs/cgroup/memory/memory.limit_in_bytes`
设置Docker的运行参数
-memorty-swappiness=0
我们显式的关闭了SWAP,就可以避免直接的OOM了
对于使用的JDK 9之后的版本,可以利用 jlink 定制最小依赖的Java运行环境,将JDK裁减为几十M的大小