Java 8 终于支持 Docker !

百家 作者:CSDN 2019-05-20 07:10:21

Java 8 过去一直与 Docker 无法很好地兼容,现在可让开发者们奔走相告的是,这个问题已经解决了。

作者 |?Grzegorz Kocur

译者 | 苏本如,责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

请注意:本文中我使用的是遵循GNU GPL v2 许可授权的OpenJDK官方Docker镜像。这里描述的对Docker的支持在Oracle Java SE 开发工具包(JDK)版本8的更新191中被引入。Oracle在2019年4月修改了Java 8更新的许可政策,自Java SE 8更新211后的商业使用不再免费。? ? ?

你是否曾经经历过在Docker中运行基于JVM的应用程序时出现“随机”故障?或者一些奇怪的死机?两者都有可能是由于Java 8(它仍然被广泛使用)中的糟糕的Docker支持引起。

Docker使用控制组(cgroups)来限制对资源的使用。在容器中运行应用程序时限制其对内存和CPU的使用绝对是一个好主意,它可以防止应用程序占用全部可用的内存和/或CPU,因而导致在同一系统上运行的其他容器无法响应。限制资源的使用可以提高应用程序的可靠性和稳定性。它还为硬件容量的规划提供了依据。在像诸如Kubernetes或DC/OS这样的编排系统上运行容器时,这一点尤为重要。


问题


JVM可以“看到”系统上所有可用的内存和CPU内核,并保持与这些资源的一致。在默认情况下,JVM会将max heap size(最大堆大小)设置为系统内存的1/4,并将一些线程池个数(比如说垃圾回收(GC))设置为与物理CPU内核的数量一致。我们一起来看看下面的例子。

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

import?java.util.Vector;
public?class?MemoryEater
{
??public?static?void?main(String[]?args)
??
{
????Vector?v?=?new?Vector();
????while?(true)
????{
??????byte?b[]?=?new?byte[1048576];
??????v.add(b);
??????Runtime?rt?=?Runtime.getRuntime();
??????System.out.println(?"free?memory:?"?+?rt.freeMemory()?);
????}
??}
}

我们在内存为64GB的系统上运行它,让我们来检查一下默认的最大堆大小:

$?docker?run?-ti?-m?512M?openjdk:8u181-jdk
root@eca214e0fcd4:/#?java?-XX:+PrintFlagsFinal?-version?|?grep?MaxHeap
????uintx?MaxHeapFreeRatio??????????????????????????=?100?????????????????????????????????{manageable}
????uintx?MaxHeapSize??????????????????????????????:=?16819159040?????????????????????????{product}
openjdk?version?"1.8.0_181"
OpenJDK?Runtime?Environment?(build?1.8.0_181-8u181-b13-2~deb9u1-b13)
OpenJDK?64-Bit?Server?VM?(build?25.181-b13,?mixed?mode)

如前所述,它应该是系统物理内存的1/4 (16GB)。如果我们使用Docker cgroups限制内存,会发生什么呢?让我们检查一下:? ? ?

$?docker?run?-ti?-m?512M?openjdk:8u181-jdk
root@eca214e0fcd4:/#?javac?MemoryEater.java
Note:?MemoryEater.java?uses?unchecked?or?unsafe?operations.
Note:?Recompile?with?-Xlint:unchecked?for?details.
root@eca214e0fcd4:/#?java?MemoryEater
free?memory:?1003980048
free?memory:?1003980048
free?memory:?1003980048
[...]
free?memory:?803562640
free?memory:?802514048
free?memory:?801465456
free?memory:?800416864
Killed
root@eca214e0fcd4:/#

这个JVM进程被终止了。因为它是一个子进程,所以容器本身幸存下来,但是通常当Java是容器内的唯一进程(用PID 1)时,容器也会崩溃。

让我们深入研究一下系统日志中有什么:

Apr?18?16:18:46?dcos-agent-1?kernel:?java?invoked?oom-killer:?gfp_mask=0xd0,?order=0,?oom_score_adj=0
Apr?18?16:18:46?dcos-agent-1?kernel:?java?cpuset=eca214e0fcd4b245eecb2a80c05e9d7f8688fc36979c510d2fb9afab2ce55712?mems_allowed=0
Apr?18?16:18:46?dcos-agent-1?kernel:?CPU:?6?PID:?4142?Comm:?java?Tainted:?G???????????????------------?T?3.10.0-693.17.1.el7.x86_64?#1
Apr?18?16:18:46?dcos-agent-1?kernel:?Hardware?name:?Supermicro?Super?Server/X10SRi-F,?BIOS?2.0?12/17/2015
Apr?18?16:18:46?dcos-agent-1?kernel:?Call?Trace:
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?dump_stack+0x19/0x1b
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?dump_header+0x90/0x229
Apr?18?16:18:46?dcos-agent-1?kernel:?[]???find_lock_task_mm+0x56/0xc0
Apr?18?16:18:46?dcos-agent-1?kernel:?[]???try_get_mem_cgroup_from_mm+0x28/0x60
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?oom_kill_process+0x254/0x3d0
Apr?18?16:18:46?dcos-agent-1?kernel:?[]???selinux_capable+0x1c/0x40
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?mem_cgroup_oom_synchronize+0x546/0x570
Apr?18?16:18:46?dcos-agent-1?kernel:?[]???mem_cgroup_charge_common+0xc0/0xc0
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?pagefault_out_of_memory+0x14/0x90
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?mm_fault_error+0x68/0x12b
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?__do_page_fault+0x391/0x450
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?do_page_fault+0x35/0x90
Apr?18?16:18:46?dcos-agent-1?kernel:?[]?page_fault+0x28/0x30
Apr?18?16:18:46?dcos-agent-1?kernel:?Task?in?/docker/eca214e0fcd4b245eecb2a80c05e9d7f8688fc36979c510d2fb9afab2ce55712?killed?as?a?result?of?limit?of?/docker/eca214e0fc
d4b245eecb2a80c05e9d7f8688fc36979c510d2fb9afab2ce55712
Apr?18?16:18:46?dcos-agent-1?kernel:?memory:?usage?524180kB,?limit?524288kB,?failcnt?314788
Apr?18?16:18:46?dcos-agent-1?kernel:?memory+swap:?usage?1048576kB,?limit?1048576kB,?failcnt?6
Apr?18?16:18:46?dcos-agent-1?kernel:?kmem:?usage?0kB,?limit?9007199254740988kB,?failcnt?0
Apr?18?16:18:46?dcos-agent-1?kernel:?Memory?cgroup?stats?for?/docker/eca214e0fcd4b245eecb2a80c05e9d7f8688fc36979c510d2fb9afab2ce55712:?cache:28KB?rss:524152KB?rss_huge
:0KB?mapped_file:0KB?swap:524396KB?inactive_anon:262176KB?active_anon:261976KB?inactive_file:8KB?active_file:4KB?unevictable:0KB
Apr?18?16:18:46?dcos-agent-1?kernel:?[?pid?]???uid??tgid?total_vm??????rss?nr_ptes?swapents?oom_score_adj?name
Apr?18?16:18:46?dcos-agent-1?kernel:?[?1400]?????0??1400?????4985??????418??????14??????139?????????????0?bash
Apr?18?16:18:46?dcos-agent-1?kernel:?[?4141]?????0??4141??4956003???126966?????606???137837?????????????0?java
Apr?18?16:18:46?dcos-agent-1?kernel:?Memory?cgroup?out?of?memory:?Kill?process?4162?(java)?score?1012?or?sacrifice?child
Apr?18?16:18:46?dcos-agent-1?kernel:?Killed?process?4141?(java)?total-vm:19824012kB,?anon-rss:495748kB,?file-rss:12116kB,?shmem-rss:0kB

像这样的故障很难调试,因为应用程序日志中没有任何内容。在像AWS ECS这样的管理系统上,它可能尤其困难。

CPU怎么样?让我们运行一个显示可用处理器数量的小程序,来再一次看看会发生什么:

public?class?AvailableProcessors?{
public?static?void?main(String[]?args)?{
//?check?the?number?of?processors?available
??????System.out.println(""+Runtime.getRuntime().availableProcessors());
???}
}

我们在一个CPU数量设置为1的Docker容器中运行这个小程序:

$?docker?run?-ti?--cpus?1?openjdk:8u181-jdk
root@82080104994c:/#?javac?AvailableProcessors.java
root@82080104994c:/#?java?AvailableProcessors
12

不好!这个系统上实际有12个CPU。因此,即使将可用处理器的数量限制为1个,JVM也将尝试使用12个。例如,垃圾回收(GC)线程数量是基于以下公式设置的:

在具有n个硬件线程并且n大于8的计算机上,并行回收器使用一个固定的分数来设定垃圾回收器的线程数。当n大于8时,这个分数约为5/8。当n小于8时,垃圾回收器的线程数为n。

在案例中:

root@82080104994c:/#?java?-XX:+PrintFlagsFinal?-version?|?grep?ParallelGCThreads
????uintx?ParallelGCThreads?????????????????????????=?10??????????????????????????????????{product}


解决方案


好的,我们现在知道这个问题的存在了。那么有解决办案吗?幸运的是 - 有!

新的Java版本(10及以上)已经内置了Docker的支持功能。但有时升级并不能解决问题,比如说,如果应用程序与新的JVM不兼容就不行。

好消息是:对Docker的支持还被向后移植到Java 8。让我们运行下面人命令来检查标记为8u212的最新openjdk?镜像。我们将内存限制为1G,并使用1个CPU:

docker?run?-ti?--cpus?1?-m?1G?openjdk:8u212-jdk

内存:

root@843e552c2e49:/#?java?-XX:+PrintFlagsFinal?-version?|?grep?MaxHeap
????uintx?MaxHeapFreeRatio??????????????????????????=?70??????????????????????????????????{manageable}
????uintx?MaxHeapSize??????????????????????????????:=?268435456???????????????????????????{product}
openjdk?version?"1.8.0_212"
OpenJDK?Runtime?Environment?(build?1.8.0_212-8u212-b01-1~deb9u1-b01)
OpenJDK?64-Bit?Server?VM?(build?25.212-b01,?mixed?mode)
root@843e552c2e49:/#

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

CPU:

root@16f12923f731:/#?java?AvailableProcessors
1

正如我们想要的那样。

此外,还有一些新设置:

-XX:InitialRAMPercentage
-XX:MaxRAMPercentage
-XX:MinRAMPercentage

这些设置允许微调 heap size(堆大小)。这些设置的含义在StackOverflow的这个优秀答案中已经得到了解释。请注意,它们设置的是百分比,而不是固定值。多亏了它,更改Docker内存设置不会破坏任何东西。

如果出于某种原因不需要新JVM的特性,可以使用-xx:-useContainerSupport关闭它。


结论


为基于JVM的应用程序设置正确的heap size(堆大小)是非常重要的。使用最新的Java 8版本,你可以依赖安全(但是非常保守)的默认设置。而不需要在Docker入口点中使用任何变通办法,也不需要再将Xmx设置为固定值。

祝大家使用 JVM愉快!?

原文:https://blog.softwaremill.com/docker-support-in-new-java-8-finally-fd595df0ca54

本文为 CSDN 翻译,转载请注明来源出处。

【END】

作为码一代,想教码二代却无从下手:

听说少儿编程很火,可它有哪些好处呢?

孩子多大开始学习比较好呢?又该如何学习呢?

最新的编程教育政策又有哪些呢?

下面给大家介绍CSDN新成员:极客宝宝(ID:geek_baby)

戳他了解更多↓↓↓

?热 文?推 荐?

?Linux 之父:我就是觉得苹果没意思!| 人物志

?量子计算淘金热

??33 关 Python 游戏,测试你的爬虫能力到底及格不?

?厉害!女学生偷师男子学校,变身区块链开发工程师

?520 这天,我突然意识到,她根本配不上我这么聪明的男人

?刷了一个半月算法题,我薪资终于Double了

?确实, 5G与物联网离不开区块链!

?掌声送给TensorFlow 2.0!用Keras搭建一个CNN | 入门教程

?中国AI开发者真实现状:写代码这条路,会走多久?

?刺激!华为程序员年薪200万 ?真相让人心酸!

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接