1. Docker ≠ VM

从某些角度看,Docker 与 虚拟机 有些类似。

如:自己的 shell、能独立安装软件包、运行时与其它容器互不干扰。

 

但 Docker 的虚拟化远没有虚拟机彻底。Docker 是一种更轻量化的隔离技术。如:

-> 用 namespace 技术为每个容器提供单独的命名空间,实现对网络、PID、用户、IPC通信、文件系统挂载点等方面的隔离;

-> 用 CGroup 技术对CPU、内存、磁盘IO等计算资源进行管理




在虚拟机平台上,任何程序(包括Java)只要调用的是同一个系统API,其行为都是一致的,无需额外考虑兼容性。

而 Docker 的不完全虚拟化则意味着部分底层细节是需要用户理解Docker的行为后作额外处理的。也就是说这部分兼容性需用户自行处理。

如,在 Docker 环境中:

-> CGroup 这种资源管理技术对 Java 是不能自动处理的。

-> namespace 这种隔离技术则导致 jcmd、jstack 等工具需要做一些修改才能正常使用。

因为这些工具依赖于“/proc/”下提供的一些信息。

 

 

2. Java程序在Docker机制下的问题

Java程序在Docker机制下的主要问题来自JVM对系统可用资源的误判

 

2.1 运行时资源误判

Docker环境中,CPU和内存等资源是通过CGroup(Control Group)实现的。JDK 在 8u131 之前不会识别这些限制,可能导致过度“索取”资源而引发问题。如:

 

2.1.1 可用内存误判

JVM启动时会根据检测的系统内存大小设置自身的相关内存使用限制。

包括堆、元数据区、直接内存等。如:

初始堆大小为系统内存的 1/64;

最大堆大小为系统内存的 1/4

 

基于误判的系统可用内存,JVM 的相关参数设置就可能不合理。

这会导致 JVM 试图使用超过容器限制的内存,进而导致运行时 JVM OOM 或 容器因 OOM 而被 kill;

 

另外,某些时候,我们会通过参数 -XX:OnOutOfMemoryError 来指定一段脚本,以便让Java 程序在 OOM 时自动重启。

但是在Docker容器环境下,如果Java程序已经过度提交内存,容器可能无法支持这种基于 fork 创建的新进程正常运行。

 

 2.1.2 CPU可用核数误判

因误判可用的CPU资源,JVM 设置的 GC 并行线程数超过了容器的限制。

JVM 会将检测到的系统 CPU 核数,作为确定 Parallel GC 并行线程数 和 JIT compiler 线程数的依据

ForkJoinPool 机制中的并行等级也会受到影响

 

2.2 其它影响

早期Java程序主要是长时间运行在大型服务器端的应用,Java早期的优化也主要针对这类场景,所以内存占用大、启动速度慢等现象并不算最大的痛点。

但当Java程序被应用到Docker化的微服务或Serverless架构中时,便凸显了这些痛点。

 

 

3. 如何解决Java程序在Docker环境中的可用资源误判问题

方案一:升级JDK

最新JDK对Docker容器的支持已经比较完善。所以这通常这是最稳妥的方案。可以避免自己去费尽心力查找各种潜在的漏洞,并给出相应的补救措施。专业事交给专业的人去做!

 

JDK 10:

JDK 10 默认自适应Docker环境中的各种资源限制和实现差异。

还可以用参数 -XX:ActiveProcessorCount 来指定 CPU 核数。

 

JDK 9:

内存方面:

通过参数 -XX:+UnlockExperimentalVMOptions 和 -XX:+UseCGroupMemoryLimitForHeap 设置堆内存限制

注:

-> UnlockExperimentalVMOptions 必须在前

-> 只在 Linux 环境有效

 

CPU方面:

JDK 9 可以正确理解 Docker 中 --cpuset-cpus 等相关CPU限制的参数。所以无需额外设置

 

JDK 8u131:

JDK 8 从该版本开始已包括 JDK 9 中对 Docker 的支持,所以可以像 JDK 9 那样设置相关参数。

 

 

方案二:继续使用当前JDK,并明确设置相关资源限制

如:通过 Docker 参数 -e 将最大堆内存设置信息放入参数 JAVA_OPTIONS,让其内部的Java程序结合该Docker环境变量作为JVM的启动参数之一。

-e JAVA_OPTIONS='-Xmx1g'

-XX:ParallelGCThreads-XX:CICompilerCount 等JVM参数也可以通过上述方法传递

 

最好也告知 JVM 最大系统内存限制。因为堆只是 JVM 所使用内存的一部分。

-XX:MaxRAM='cat /sys/fs/cgroup/memory/memory.limit_in_bytes'

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐