陈奇网络工作室

Java和Docker限制

云计算

作者: kelvinjin2009来源:程序员

原文链接:

33558 www.tech ug.com/post/Java-and-docker-memory-limits.html

Java和Docker不是天然的朋友。 Docker可以设置内存和CPU限制,但无法自动检测到Java。 使用Java的Xmx标记(复杂/重复)或新的实验性JVM标记可以解决此问题。

虚拟化不一致

Java和Docker的组合并不是完美的匹配,最初离完美的匹配还有相当大的距离。 对于初学者来说,JVM的所有构想都是虚拟机可以运行程序,而无需考虑底层硬件。

那么,将我们的Java APP打包成JVM,然后将其整个塞进docker容器中,能给我们带来什么好处呢? 大多数情况下,你只是在复制JVMs和Linux容器,除了浪费更多的内存外,没有任何好处。 我觉得这个样子很傻。

但是,Docker可以将您的程序、设置、特定的JDK、Linux设置和APP应用服务器以及其他工具放在一起。 从DevOps/Cloud的角度来看,这种完整的容器具有更高水平的封装。

问题1 :内存

大多数产品级APP应用程序仍然使用Java 8(或更早的版本),这可能会导致问题。 Java8(update131之前的版本)无法很好地与Docker一起工作。 问题是在你的机器上,JVM的可用内存和CPU数量不是Docker可用的内存和CPU数量。

例如,如果您限制Docker容器只能有100MB的内存,但是,旧版本的Java无法识别此限制。 Java看不到这个限制。 JVM需要更多的内存,远远超过了这个限制。 如果内存使用过多,Docker将采取行动,杀死容器中的进程! JAVA进程被杀。 很明显,这不是我们想要的。

要解决此问题,必须为Java指定最大内存限制。 在早期版本的Java(8u131及更低版本)中,必须通过将容器设置为-Xmx来限制堆大小。 我觉得这不太正确。 我不想两次定义这些限制,也不想在容器中定义这些限制。

幸运的是,我们现在有了更好的方法来解决这个问题。 从Java 9之后( 8u131 ),JVM添加了以下标记:

- xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheap

这些标志强制JVM检查Linux的cgroup配置,Docker通过cgroup实现最大内存设置。 现在,如果您的APP应用程序达到了Docker设置的限制(例如500MB ),JVM将看到此限制。 JVM将尝试GC操作。 如果仍然超过内存限制,JVM将做它应该做的事情,并抛出OutOfMemoryException。 也就是说,JVM可以看到Docker的这些设置。

从Java 10开始(请参见以下测试),这些体验标志位在缺省情况下处于启用状态。 也可以使用-XX: UseContainerSupport启用。 可以通过设置-XX:-UseContainerSupport来禁止这些行为。

问题2 :处理器

第二个问题类似,但与CPU有关。 这意味着JVM将检查硬件并检测CPU的数量。 优化运行时间以使用这些CPUs。 但在同样的情况下,这里还有另一个不一致之处。 Docker可能不允许使用所有这些CPUs。 遗憾的是,这在Java 8或Java 9中没有修复,但在Java 10中已修复。

从Java 10开始,可用的CPU计算通过使用UseContainerSupport以各种方式(缺省)解决了此问题。

Java和Docker内存处理测试

作为一个有趣的练习,让我们来验证和测试Docker如何使用几个不同的JVM版本/标志,甚至是不同的JVM来处理内存不足。

首先,创建一个测试APP应用程序,它只“吃”内存,而不释放内存。

javaimportjava.util.ArrayList; importjava.util.List; publicclassMemEat{

publicstaticvoidmain ( string [ ] args ) {

Listl=newArrayList (;

while (真)。

byteb[]=newbyte[1048576];

l.add(b );

Runtimert=Runtime.getRuntime (;

system.out.println(& #039; freememory:& #039; rt.freememory );

}

}

在启动Docker容器并执行此APP应用程序后,您可以看到发生了什么。

测试Java 8u111

首先,从具有旧Java 8版本的容器( update 111 )开始。

壳牌

docker run-m100m-it Java:open JDK-8 u111/bin/bash

编译并运行MemEat.java文件。

壳牌

javacMemEat.java

Java meme at . free memory:67194416 free memory:66145824 free memory:65097232 killed

正如所料,Docker杀死了我们的Java进程。 不是我们想要的(! 请参阅。 也可以看到输出。 Java认为还需要分配大量的内存。

可以通过使用-Xmx标志为Java提供最大内存来解决此问题。

壳牌

javacMemEat.java

jva-xmx 100 Mme meat . free memory:1155664 free memory:1679936 free memory:2204208 free memory:1315752 exceptioninthread & amp #039; m亚马逊

atmemeat.main(memeat.Java:8 ) )。

提供自己的内存限制后,进程正常停止,JVM了解正在执行的限制。 但是,问题是你现在设定了两次这些内存限制,一次是Docker,一次是JVM。

测试Java 8u144

如上所述,通过添加新标志来修复问题,JVM现在可以遵循Docker提供的设置。 可以使用新版本的JVM进行测试。

壳牌

docker run-m100m-itadoptopenjdk/open JDK8/bin/bash

撰写本文时,此OpenJDK Java镜像的版本为Java 8u144。)

然后再次编译并运行不带标志的MemEat.java文件。

壳牌

javacMemEat.java

Java meme at . free memory:67194416 free memory:66145824 free memory:65097232 killed

仍然有同样的问题。 但是,让我们现在提供一下上述实验性的标识:

壳牌

javac MemEat.java

Java-xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheapmemeat

……

free memory: 1679936

free memory: 2204208

free memory: 1155616

free memory: 1155600

扩展& amp; #039; main& #039; Java.lang.out of memory error:javaheapspace

atmemeat.main(memeat.Java:8 ) )。

这次我没有告诉你JVM的限制是什么。 我只是告诉JVM检查正确的限制设置。 现在感觉好多了。

测试Java 10u23

一些人在评论和Reddit中表示,Java 10通过将实验标志设置为新的缺省值来解决所有问题。 可以通过禁用此标志来关闭此行为:-XX:-UseContainerSupport。

我测试那个的时候,那个一开始不起作用。 撰写本文时,AdoptAJDK OpenJDK10镜像与jdk-10 23打包在一起。 这个JVM显然不理解UseContainerSupport标志,这个过程仍然被Docker杀死。

壳牌

docker run-m100m-itadoptopenjdk/open JDK 10/bin/bash

我测试了代码(我也手动提供过所需的标志)。

壳牌

javacMemEat.java

Java meme at . free memory:96262112 free memory:94164960 free memory:92067808 free memory:89970656 killed

Java-xx:usecontainersupportmemeat

unrecognizedvmoption& #039; usecontainersupport& #039; error:couldnotcreatethejavavirtualmachine.error:afatalexceptionhasoccurred

测试4:Java10u46(nightly ) )。

我决定尝试最新的adopt a JDK open JDK 10 nightly内部版本。 它包含的是Java 10 46的版本,而不是Java 10 23。

壳牌

docker run-m100m-itadoptopenjdk/open JDK 10:nightly/bin/bash

但是,此ngithly内部版本存在一个问题,即导出的PATH指向旧的Java 10 23目录而不是10 46,需要更正此问题。

壳牌

export path=$ path:/opt/Java/open JDK/JDK-1046/bin/javacmemeat.Java

Java meme at . free memory:3566824 free memory:2796008 free memory:1480320 exceptioninthread & amp; #039; main& #039; Java.lang.outofmemory

atmemeat.main(memeat.Java:8 ) )。

成功! 即使不指定标志,Java 10也能正确检测Dockers内存限制。

测试OpenJ9

我最近也在尝试OpenJ9。 这个免费的备用JVM已经从IBM J9开放源代码化,现在由Eclipse维护。

请在我的下一篇博文( 3358 royvanrijn.com/blog/2018/05/openj9- JVM-shootout/)中阅读openj9的详细内容。

运行速度快,内存管理非常好,性能出色,经常可以节约多达30-50%的微服务器内存。 由此,spring boot APP应用程序几乎可以定义为“micro”,其执行时间仅为100-200mb而不是300mb。 我打算尽快写一篇关于这方面的文章。

但令人惊讶的是,OpenJ9还没有用于Java 8/9/10的cgroup内存限制的类似标志( backported )的选项。 如果将以前的测试用例应用于最新的AdoptAJDK OpenJDK 9 OpenJ9 build :

壳牌

docker run-m100m-itadoptopenjdk/open JDK9- openj9/bin/bash

添加OpenJDK标记(在OpenJ9中被忽略的标记)。

壳牌

jva-xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheapmemeat . free memory:83988984 free memory:82940400

ops,JVM再次被Docker杀害了。

我希望立即向OpenJ9添加类似的选项。 因为我想在生产环境中运行此选项,而不是指定两次最大内存。 Eclipse/IBM正在努力解决这个问题,已经提出了issues,并向issues提出了宣传。

更新: (不推荐使用Hack ) ) ) ) )。

用有点难看/黑客的方法解决这个问题的方法是使用下面的组合标志。

壳牌

Java-xmx ` cat/sys/fs/cgroup/memory.limit _ in _ bytes ` meat. free memory:3171536 free memory:2127027 #039; syssing Etail& #039; Java/Lang/outofmemoryerror& #039; at 2018/05/1514:04:26-please wait.JVM dump 032 ijvmrequestedsystemdumpusing & amp; #039; core.20180515.140426.125.0001.DMP & amp; #039; inresponsetoaneventjvmdump 010 isystemdumpwrittento/core.20180515.140426.125.0001.dmpjvmrequestedheapdumpdump 032 #039; //heap dump.20180515.140426.125.0002.PhD & amp; #039; inresponsetoaneventjvmdump 010 iheapdumpwrittento/heap dump.20180515.140426.125.0002.phdjvmrequestedjavadump 032 #039; //jva core.20180515.140426.125.0003.txt & amp; #039; inresponsetoaneventjvmdump 010 ijavadumpwrittento/javacore.20180515.140426.125.0003.txtjvmrequestedsnapdump 032 #039; //snap.20180515.140426.125.0004.TRC & amp; #039; inresponsetoaneventjvmdump 010 isnapdumpwrittento/snap.20180515.140426.125.0004.trcjvmdump 013 iprocesseddumpevent & AAP #039; 系统和地图; #039;详细信息& amp; #039; Java/Lang/outofmemoryerroor

atmemeat.main(memeat.Java:8 ) )。

在这种情况下,堆大小限制为分配给Docker实例的内存,并应用于旧的JVM和OpenJ9。 这当然是错误的,因为容器本身和堆外的JVM的其他部分也使用了内存。 但它似乎在起作用,显然Docker在这种情况下很松散。 有些bash大神可能会制作更好的版本,从其他流程的字节中扣除一部分。

无论如何,请不要这样做。 它可能不能正常工作。

测试6:openj9(nightly ) )。

建议使用OpenJ9的最新nightly版本。

壳牌

docker run-m100m-itadoptopenjdk/open JDK9- openj9: nightly/bin/bash

最新的OpenJ9夜晚版本。 有两个东西。

另一个有问题的PATH参数需要首先解决这个问题

JVM支持新的徽标UseContainerSupport,如Java 10所示

壳牌

export path=$ path:/opt/Java/open JDK/JDK-9.0.412/bin/javacmemeat.Java

jva-xx:usecontainersupportmemeat . free memory:5864464 free memory:4815880 free memory:3443712 free memory:2391032 jvmdury #039; Java/Lang/outofmemoryerror& #039; at 2018/05/1521:32:07-please wait.JVM dump 032 ijvmrequestedsystemdumpusing & amp; #039; core.20180515.213207.62.0001.DMP & amp; #039; inresponsetoaneventjvmdump 010 isystemdumpwrittento/core.20180515.213207.62.0001.dmpjvmrequestedheapdumpump 032 #039; //heap dump.20180515.213207.62.0002.PhD & amp; #039; inresponsetoaneventjvmdump 010 iheapdumpwrittento/heap dump.20180515.213207.62.0002.phdjvmrequestedjavadump 032 #039; //jva core.20180515.213207.62.0003.txt & amp; #039; inresponsetoaneventjvmdump 010 ijavadumpwrittento/jva core.20180515.213207.62.0003.txtjvmrequestedsnapdumpdump 032 #039; //snap.20180515.213207.62.0004.TRC & amp; #039; inresponsetoaneventjvmdump 010 isnapdumpwrittento/snap.20180515.213207.62.0004.trcjvmdump 013 iprocessedumpevent & ammamp #039; 系统和地图; #039;详细信息& amp; #039; Java/lang/outofmemoryerror

TADAAA,正在修复!

奇怪的是,在OpenJ9中缺省情况下未启用此标志。 与Java 10相同。 再一次:确保您已经测试了这是您想要在Docker容器中运行Java的内容。

结论

简单来说,请注意资源限制的不一致。 测试你的内存设置和JVM标志,不要假设任何事情。

在Docker容器中运行Java时,请设置Docker内存限制,并确保在JVM中也设置了限制,或者JVM可以理解这些限制。

如果无法升级Java版本,请使用-Xmx设置自己的限制。

对于Java 8和Java 9,请更新为最新版本后使用。

- xx:unlockexperimentalvmoptions-xx:usecgroupmemorylimitforheap

对于Java 10,请确保支持" UseContainerSupport "“更新为最新版本”。

对于OpenJ9,强烈建议使用-Xmx设置限制,但不久将出现支持UseContainerSupport标志的版本。

详情请访问云服务器、域名注册、虚拟主机的问题,请访问西部数码代理商官方网站: www.chenqinet.cn

相关推荐

后台-系统设置-扩展变量-手机广告位-内容页底部广告位3