欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java 8 终于支持 Docker!

程序员文章站 2022-06-05 20:21:00
Java 8曾经与Docker无法很好地兼容性,现在问题已消失。 请注意:我在本文中使用采用GNU GPL v2许可证的OpenJDK官方docker映像。在Oracle Java SE中,这里描述的docker支持功能在更新191中引入。Oracle在2019年4月更改了Java 8更新的许可证, ......

java 8曾经与docker无法很好地兼容性,现在问题已消失。

请注意:我在本文中使用采用gnu gpl v2许可证的openjdk官方docker映像。在oracle java se中,这里描述的docker支持功能在更新191中引入。oracle在2019年4月更改了java 8更新的许可证,自java se 8 update 211以来商业使用不再免费。

你是否遇到过在docker中运行的基于jvm的应用程序出现“随机”故障?或者也许是一些奇怪的死机?两者都可能是java 8(仍广泛使用的)中糟糕的docker支持引起的。

docker使用控制组(cgroups)来限制资源。在容器中运行应用程序时限制内存和cpu绝对是个好主意――它可以阻止应用程序占用整个可用内存及/或cpu,这会导致在同一个系统上运行的其他容器毫无反应。限制资源可提高应用程序的可靠性和稳定性。它还允许为硬件容量作好规划。在kubernetes或dc/os之类的编排系统上运行容器时尤为重要。

问题

jvm可以“看到”系统上的整个内存和可用的所有cpu核心,并确保与资源一致。它默认情况下将最大堆大小(heap size)设置为系统内存的1/4,并将某些线程池大小(比如针对gc)设置为物理核心数量。不妨举例说明。

我们将运行一个简单的应用程序,它消耗尽可能多的内存(可在该网站上找到):

Java 8 终于支持 Docker!

我们在拥有64gb内存的系统上运行,所以不妨检查默认的最大堆大小:

如上所述,它是物理内存的1/4即16gb。如果我们使用docker cgroups限制内存,会发生什么?不妨检查一下:

Java 8 终于支持 Docker!

jvm进程被杀死了。由于它是一个子进程――容器本身幸存下来,但通常当java是容器(pid 1)内的唯一进程时,容器会崩溃。

不妨深入看看系统日志:

Java 8 终于支持 Docker!

Java 8 终于支持 Docker!

像这样的故障调试起来可能很难――应用程序日志中没有任何内容。在aws ecs之类的托管系统上尤其困难重重。

cpu怎么样?不妨再次检查,运行一个显示可用处理器数量的小程序:

不妨在一个cpu编号设置为1的docker容器中运行它:

不好,这个系统上的确有12个cpu。因此,即使可用处理器的数量限制为1,jvm也会尝试使用12――比如说,gc线程数量由该公式设置:

在拥有n个硬件线程(n大于8)的机器上,并行收集器使用n的固定分数作为垃圾收集器线程的数量。如果n的值很大,该分数约5/8。如果n的值低于8,使用的数字是n。

在我们的情况下:

解决方案

ok,我们现在意识到了这个问题。有解决方案吗?幸运的是,有!

新的java版本(10及以上版本)已经内置了docker支持功能。但有时升级不是办法,比如说如果应用程序与新jvm不兼容就不行。

好消息:docker支持还被向后移植到java 8。不妨检查标记为8u212的最新openjdk映像。我们将内存限制为1g,并使用1个cpu:docker run -ti --cpus 1 -m 1g openjdk:8u212-jdk。

内存:

它是256m,正好是已分配内存的1/4。

cpu:

正如我们想要的那样。

此外,还有几个新的设置:

它们允许微调堆大小――这些设置的含义在*的这个优秀答案中已得到了解释。请注意:他们设置的是百分比,而不是固定值。正因为如此,改变docker内存设置不会破坏任何东西。

如果由于某种原因不想要看到新的jvm行为,可以使用-xx:-usecontainersupport来关闭。

总结

为基于jvm的应用程序设置正确的堆大小极其重要。如果使用最新的java 8版本,你可以依赖安全(但非常保守)的默认设置。不需要在docker入口点中使用任何变通办法,也不需要再将xmx设置为固定值。

使用jvm愉快!